diff --git a/Filelist b/Filelist index e97e17ec0e..3a2e066e33 100644 --- a/Filelist +++ b/Filelist @@ -537,6 +537,7 @@ SRC_UNIX = \ src/vimtutor \ src/gvimtutor \ src/wayland.c \ + src/wayland.h \ src/which.sh \ src/gen-wayland-protocols.sh \ src/xxd/Makefile \ diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 418bc47e51..e020d8852b 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.1. Last change: 2025 Aug 24 +*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 01 VIM REFERENCE MANUAL by Bram Moolenaar @@ -13161,7 +13161,9 @@ vreplace Compiled with |gR| and |gr| commands. (always true) vtp Compiled for vcon support |+vtp| (check vcon to find out if it works in the current console). wayland Compiled with Wayland protocol support. -wayland_clipboard Compiled with support for Wayland selections/clipboard +wayland_clipboard Compiled with support for Wayland clipboard. +wayland_focus_steal Compiled with support for Wayland clipboard focus + stealing. wildignore Compiled with 'wildignore' option. wildmenu Compiled with 'wildmenu' option. win16 old version for MS-Windows 3.1 (always false) diff --git a/runtime/doc/gui_x11.txt b/runtime/doc/gui_x11.txt index e6c7447446..2925a33e19 100644 --- a/runtime/doc/gui_x11.txt +++ b/runtime/doc/gui_x11.txt @@ -1,4 +1,4 @@ -*gui_x11.txt* For Vim version 9.1. Last change: 2025 Aug 10 +*gui_x11.txt* For Vim version 9.1. Last change: 2025 Sep 01 VIM REFERENCE MANUAL by Bram Moolenaar @@ -709,8 +709,8 @@ overwriting selected text. *W23* When you are yanking into the "* or "+ register and Vim cannot establish a -connection to the X11 selection (or clipboard), it will use register 0 and -output a warning: +connection to the X11 selection (or X11/Wayland clipboard), it will use +register 0 and output a warning: Warning: Clipboard register not available, using register 0 ~ diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index bd4383e175..23607a3ad3 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 Aug 27 +*options.txt* For Vim version 9.1. Last change: 2025 Sep 01 VIM REFERENCE MANUAL by Bram Moolenaar @@ -10195,7 +10195,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'wlsteal'* *'wst'* *'nowlsteal'* *'nowst'* 'wlsteal' 'wst' boolean (default off) global - {only when the |+wayland_clipboard| feature is included} + {only when the |+wayland_focus_steal| feature is + included} When enabled, then allow Vim to steal focus by creating a temporary surface, in order to access the clipboard. For more information see |wayland-focus-steal|. diff --git a/runtime/doc/tags b/runtime/doc/tags index 37484a1d17..617753221c 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1542,6 +1542,7 @@ $quote eval.txt /*$quote* +vtp various.txt /*+vtp* +wayland various.txt /*+wayland* +wayland_clipboard various.txt /*+wayland_clipboard* ++wayland_focus_steal various.txt /*+wayland_focus_steal* +wildignore various.txt /*+wildignore* +wildmenu various.txt /*+wildmenu* +windows various.txt /*+windows* diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 9d631ad92f..16bab26932 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -1,4 +1,4 @@ -*various.txt* For Vim version 9.1. Last change: 2025 Aug 18 +*various.txt* For Vim version 9.1. Last change: 2025 Sep 01 VIM REFERENCE MANUAL by Bram Moolenaar @@ -528,6 +528,8 @@ T *+vreplace* |gR| and |gr| *+vtp* on MS-Windows console: support for 'termguicolors' N *+wayland* Unix only: support for the Wayland protocol. N *+wayland_clipboard* Unix only: support for Wayland selections/clipboard. +N *+wayland_focus_steal* Unix only: support for Wayland clipboard on + compositors without a data control protocol. T *+wildignore* 'wildignore' Always enabled since 9.0.0278 T *+wildmenu* 'wildmenu' Always enabled since 9.0.0279 T *+windows* more than one window; Always enabled since 8.0.1118. diff --git a/runtime/doc/wayland.txt b/runtime/doc/wayland.txt index d4c85c698c..eda3e25752 100644 --- a/runtime/doc/wayland.txt +++ b/runtime/doc/wayland.txt @@ -1,4 +1,4 @@ -*wayland.txt* For Vim version 9.1. Last change: 2025 Aug 27 +*wayland.txt* For Vim version 9.1. Last change: 2025 Sep 01 VIM REFERENCE MANUAL by Bram Moolenaar @@ -105,7 +105,8 @@ To solve this problem, Vim implements a way of gaining focus in order to access the clipboard, by creating a temporary transparent top-level surface. This is by default disabled and can be enabled via the 'wlsteal' option. Moreover, a seat that has a keyboard is also required, see 'wlseat', and the -xdg-shell protocol must be available. +xdg-shell protocol must be available. Additionally, Vim must be compiled with +the |+wayland_focus_steal| feature. Note that this method can have several side effects from the result of focus stealing. For example, if you have a taskbar that shows currently opened apps diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 9c706dce71..faa90bb121 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project -" Last Change: 2025 Aug 23 +" Last Change: 2025 Sep 01 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -822,7 +822,7 @@ if has('wayland') call AddOption("wlseat", gettext("Wayland seat to use")) call OptionG("wse", &wse) endif -if has("wayland_clipboard") +if has("wayland_focus_steal") call AddOption("wlsteal", gettext("Enable wayland focus stealing functionality in order to access the clipboard")) call BinOptionG("wst", &wst) endif diff --git a/src/Makefile b/src/Makefile index d1b08be257..2bebd50081 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1646,7 +1646,9 @@ MESSAGE_TEST_TARGET = message_test$(EXEEXT) UNITTEST_SRC = $(JSON_TEST_SRC) $(KWORD_TEST_SRC) $(MEMFILE_TEST_SRC) $(MESSAGE_TEST_SRC) UNITTEST_TARGETS = $(JSON_TEST_TARGET) $(KWORD_TEST_TARGET) $(MEMFILE_TEST_TARGET) $(MESSAGE_TEST_TARGET) -RUN_UNITTESTS = run_json_test run_kword_test run_memfile_test run_message_test +# We need to put WAYLAND_SRC because the protocol files need to be generated +# else wayland.h will error +RUN_UNITTESTS = $(WAYLAND_SRC) run_json_test run_kword_test run_memfile_test run_message_test # All sources, also the ones that are not configured ALL_LOCAL_SRC = $(BASIC_SRC) $(ALL_GUI_SRC) $(UNITTEST_SRC) $(EXTRA_SRC) \ @@ -3861,9 +3863,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 \ @@ -4564,7 +4566,7 @@ objects/wayland.o: wayland.c vim.h protodef.h auto/config.h feature.h os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ structs.h regexp.h gui.h libvterm/include/vterm.h \ libvterm/include/vterm_keycodes.h xdiff/xdiff.h xdiff/../vim.h alloc.h \ - ex_cmds.h spell.h proto.h globals.h errors.h \ + ex_cmds.h spell.h proto.h globals.h errors.h wayland.h \ auto/wayland/wlr-data-control-unstable-v1.h \ auto/wayland/ext-data-control-v1.h auto/wayland/xdg-shell.h \ auto/wayland/primary-selection-unstable-v1.h diff --git a/src/auto/configure b/src/auto/configure index 5c0d3d614f..24bbebc9fd 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -862,6 +862,7 @@ enable_farsi enable_xim enable_fontset with_wayland +enable_wayland_focus_steal with_x enable_gui enable_gtk2_check @@ -1542,6 +1543,9 @@ Optional Features: --disable-farsi Deprecated. --enable-xim Include XIM input support. --enable-fontset Include X fontset output support. + --enable-wayland-focus-steal + Include focus stealing support for Wayland + clipboard. --enable-gui=OPTS X11 GUI. default=auto OPTS=auto/no/gtk2/gnome2/gtk3/motif/haiku/photon/carbon --enable-gtk2-check If auto-select GUI, check for GTK+ 2 default=yes --enable-gnome-check If GTK GUI, check for GNOME default=no @@ -9271,13 +9275,39 @@ fi if test "$with_wayland" = yes; then -cppflags_save=$CPPFLAGS -cflags_save=$CFLAGS + cppflags_save=$CPPFLAGS + cflags_save=$CFLAGS + { printf "%s\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 : + WAYLAND_SRC+=" \ + auto/wayland/xdg-shell.c \ + auto/wayland/primary-selection-unstable-v1.c" + WAYLAND_OBJ+=" \ + objects/xdg-shell.o \ + objects/primary-selection-unstable-v1.o" +fi + + diff --git a/src/clipboard.c b/src/clipboard.c index 4b2e6874a9..b96080207b 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -32,6 +32,80 @@ #if defined(FEAT_CLIPBOARD) || defined(PROTO) #if defined(FEAT_WAYLAND_CLIPBOARD) + +# include "wayland.h" + +# ifdef FEAT_WAYLAND_CLIPBOARD_FS + +// Structures used for focus stealing +typedef struct { + struct wl_shm_pool *pool; + int fd; + + struct wl_buffer *buffer; + bool available; + + int width; + int height; + int stride; + int size; +} clip_wl_buffer_store_T; + +typedef struct { + void *user_data; + void (*on_focus)(void *data, uint32_t serial); + + struct wl_surface *surface; + struct wl_keyboard *keyboard; + + struct { + struct xdg_surface *surface; + struct xdg_toplevel *toplevel; + } shell; + + bool got_focus; +} clip_wl_fs_surface_T; // fs = focus steal + +# endif // FEAT_WAYLAND_CLIPBOARD_FS + +// Represents either the regular or primary selection +typedef struct { + char_u *contents; // Non-null if we own selection, + // contains the data to send to other + // clients. + vwl_data_source_T *source; // Non-NULL if we own the selection, + // else NULL if we don't. + vwl_data_offer_T *offer; // Current offer for the selection + +# ifdef FEAT_WAYLAND_CLIPBOARD_FS + bool requires_focus; // If focus needs to be given to us to + // work +# endif + bool own_success; // Used by clip_wl_own_selection() + bool available; // If selection is ready to serve/use + + // These may point to the same proxy as the other selection + vwl_data_device_manager_T *manager; + vwl_data_device_T *device; +} clip_wl_selection_T; + +// Represents the clipboard for the global Wayland connection, for the chosen +// seat (using the 'wl_seat' option) +typedef struct { + vwl_seat_T *seat; + +#ifdef FEAT_WAYLAND_CLIPBOARD_FS + clip_wl_buffer_store_T *fs_buffer; +#endif + + clip_wl_selection_T regular; + clip_wl_selection_T primary; + + // Array of file descriptors of clients we are sending data to. These should + // be polled for POLLOUT and have the respective callback called for each. + garray_T write_fds; +} clip_wl_T; + // Mime types we support sending and receiving // Mimes with a lower index in the array are prioritized first when we are // receiving data. @@ -45,21 +119,20 @@ static const char *supported_mimes[] = { "TEXT" }; -static void clip_wl_receive_data(Clipboard_T *cbd, - const char *mime_type, int fd); +clip_wl_T clip_wl; + +static void +clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd); static void clip_wl_request_selection(Clipboard_T *cbd); -static void clip_wl_send_data(const char *mime_type, int fd, - wayland_selection_T); static int clip_wl_own_selection(Clipboard_T *cbd); static void clip_wl_lose_selection(Clipboard_T *cbd); static void clip_wl_set_selection(Clipboard_T *cbd); -static void clip_wl_selection_cancelled(wayland_selection_T selection); -#if defined(USE_SYSTEM) && defined(PROTO) -static int clip_wl_owner_exists(Clipboard_T *cbd); -#endif +# if defined(USE_SYSTEM) && defined(PROTO) +static bool clip_wl_owner_exists(Clipboard_T *cbd); +# endif -#endif +#endif // FEAT_WAYLAND_CLIPBOARD /* * Selection stuff using Visual mode, for cutting and pasting text to other @@ -99,6 +172,24 @@ skip: } } +#ifdef FEAT_WAYLAND_CLIPBOARD + 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; +} +#endif + /* * 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 +2298,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 +2335,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 +2360,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". @@ -2337,7 +2441,594 @@ adjust_clip_reg(int *rp) } } -#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; + clip_init_single(&clip_plus, FALSE); + } + else if (device == clip_wl.primary.device) + { + sel = &clip_wl.primary; + clip_wl.primary.device = NULL; + clip_init_single(&clip_star, FALSE); + } + 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); + + if (clipmethod == CLIPMETHOD_WAYLAND) + { + if (clip_wl.regular.available) + clip_init_single(&clip_plus, TRUE); + if (clip_wl.primary.available) + clip_init_single(&clip_star, TRUE); + } + + return OK; +} + + void +clip_uninit_wayland(void) +{ + clip_wl_selection_T *sel; + +#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; + } + + if (clipmethod == CLIPMETHOD_WAYLAND) + clip_init(FALSE); + + vim_memset(&clip_wl, 0, sizeof(clip_wl)); +} + + int +clip_reset_wayland(void) +{ + if (clipmethod == CLIPMETHOD_WAYLAND) + { + if (clip_star.owned) + clip_lose_selection(&clip_star); + if (clip_plus.owned) + clip_lose_selection(&clip_plus); + } + clip_uninit_wayland(); + + if (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 +3041,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 +3059,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 +3080,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 +3154,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; + 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; - if (cbd == &clip_star) - selection = WAYLAND_SELECTION_PRIMARY; - else if (cbd == &clip_plus) - selection = WAYLAND_SELECTION_REGULAR; + if (!sel->available) + goto clear; + +#ifdef FEAT_WAYLAND_CLIPBOARD_FS + if (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 (clip_wl_init_fs_surface(clip_wl.seat, + clip_wl.fs_buffer, NULL, NULL) == FAIL) + goto clear; + } else - return; - - // Get mime types that the source client offers - mime_types = wayland_cb_get_mime_types(selection); - - if (mime_types == NULL || mime_types->ga_len == 0) +#endif { - // Selection is empty/cleared - clip_free_selection(cbd); - return; + // 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++) + mime_types_len = sel->offer->mime_types.ga_len; + mime_types = sel->offer->mime_types.ga_data; + + // 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->ga_len && chosen_mime == NULL; k++) - { - char *mime_type = ((char**)mime_types->ga_data)[k]; - - if (STRCMP(mime_type, supported_mimes[i]) == 0) + 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) +vwl_data_source_listener_event_send( + void *data, + vwl_data_source_T *source UNUSED, + const char *mime_type, + int32_t fd +) { - 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; + 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; - } + ssize_t w = write(fd, string + total, length - total); - if (written == -1) - break; - total += written; - -#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 +3340,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; } /* @@ -2701,15 +3428,18 @@ clip_wl_set_selection(Clipboard_T *cbd UNUSED) #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 @@ -2745,7 +3475,7 @@ 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; #endif } @@ -2843,7 +3573,7 @@ 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 diff --git a/src/config.h.in b/src/config.h.in index e8775d98c3..983f186b8d 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -12,6 +12,9 @@ /* Define unless no Wayland support found */ #undef HAVE_WAYLAND +/* Define if you want focus stealing support with Wayland clipboard */ +#undef FEAT_WAYLAND_CLIPBOARD_FS + /* Define when terminfo support found */ #undef TERMINFO diff --git a/src/configure.ac b/src/configure.ac index 983c582247..c00c063eea 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -2464,11 +2464,25 @@ AC_ARG_WITH(wayland, AC_MSG_RESULT([yes])])) if test "$with_wayland" = yes; then -cppflags_save=$CPPFLAGS -cflags_save=$CFLAGS + cppflags_save=$CPPFLAGS + cflags_save=$CFLAGS + AC_MSG_CHECKING(for wayland) if "$PKG_CONFIG" --exists 'wayland-client'; then AC_MSG_RESULT([yes]) + + AC_MSG_CHECKING(--enable-wayland-focus-steal argument) + AC_ARG_ENABLE(wayland-focus-steal, + [AS_HELP_STRING([--enable-wayland-focus-steal], + [Include focus stealing support for Wayland clipboard.])], + [enable_wayland_fs=$enableval], + enable_wayland_fs="yes") + + AS_IF([test "$enable_wayland_fs" = "yes"], + [AC_MSG_RESULT([yes]) + AC_DEFINE(FEAT_WAYLAND_CLIPBOARD_FS)], + [AC_MSG_RESULT([no])]) + AC_DEFINE(HAVE_WAYLAND) WAYLAND_CPPFLAGS=`$PKG_CONFIG --cflags-only-I wayland-client` WAYLAND_CFLAGS=`$PKG_CONFIG --cflags-only-other wayland-client` @@ -2478,15 +2492,20 @@ cflags_save=$CFLAGS WAYLAND_SRC=" \ auto/wayland/wlr-data-control-unstable-v1.c \ auto/wayland/ext-data-control-v1.c \ - auto/wayland/xdg-shell.c \ - auto/wayland/primary-selection-unstable-v1.c \ wayland.c" WAYLAND_OBJ=" \ objects/wlr-data-control-unstable-v1.o \ objects/ext-data-control-v1.o \ - objects/xdg-shell.o \ - objects/primary-selection-unstable-v1.o \ objects/wayland.o" + + AS_IF([test "$enable_wayland_fs" = "yes"], + [WAYLAND_SRC+=" \ + auto/wayland/xdg-shell.c \ + auto/wayland/primary-selection-unstable-v1.c" + WAYLAND_OBJ+=" \ + objects/xdg-shell.o \ + objects/primary-selection-unstable-v1.o"]) + AC_SUBST(WAYLAND_CPPFLAGS) AC_SUBST(WAYLAND_CFLAGS) AC_SUBST(WAYLAND_LIBS) diff --git a/src/evalfunc.c b/src/evalfunc.c index 3a0e9a77af..c788a2bb65 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -7615,6 +7615,13 @@ f_has(typval_T *argvars, typval_T *rettv) 1 #else 0 +#endif + }, + {"wayland_focus_steal", +#ifdef FEAT_WAYLAND_CLIPBOARD_FS + 1 +#else + 0 #endif }, {"wildignore", 1}, diff --git a/src/feature.h b/src/feature.h index 826adbe2bd..d7db96cd57 100644 --- a/src/feature.h +++ b/src/feature.h @@ -928,6 +928,13 @@ # endif #endif +/* + * +wayland_focus_steal Focus stealing support for Wayland clipboard + */ +#if !defined(FEAT_WAYLAND_CLIPBOARD) && defined(FEAT_WAYLAND_CLIPBOARD_FS) +# undef FEAT_WAYLAND_CLIPBOARD_FS +#endif + /* * +dnd Drag'n'drop support. Always used for the GTK+ GUI. */ diff --git a/src/globals.h b/src/globals.h index 8d031028ea..022176d904 100644 --- a/src/globals.h +++ b/src/globals.h @@ -2080,14 +2080,17 @@ EXTERN clipmethod_T clipmethod INIT(= CLIPMETHOD_NONE); #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-" where +// is the PID of the Vim process. Set in main.c +EXTERN char wayland_vim_special_mime[ + sizeof("application/x-vim-instance-") + NUMBUFLEN - 1]; // Includes NUL + +// Don't connect to Wayland compositor if TRUE +EXTERN int wayland_no_connect INIT(= FALSE); #endif diff --git a/src/main.c b/src/main.c index 942a7e5f8f..eddfb9e3dd 100644 --- a/src/main.c +++ b/src/main.c @@ -682,15 +682,18 @@ vim_main2(void) if (!gui.in_use) # endif { - if (wayland_init_client(wayland_display_name) == OK) + sprintf(wayland_vim_special_mime, "application/x-vim-instance-%ld", + mch_get_pid()); + + if (wayland_init_connection(wayland_display_name) == OK) { TIME_MSG("connected to Wayland display"); # ifdef FEAT_WAYLAND_CLIPBOARD - if (wayland_cb_init((char*)p_wse) == OK) + if (clip_init_wayland() == OK) TIME_MSG("setup Wayland clipboard"); - } # endif + } } #endif diff --git a/src/option.c b/src/option.c index f258836ec4..99083d0d3a 100644 --- a/src/option.c +++ b/src/option.c @@ -4770,7 +4770,7 @@ did_set_winwidth(optset_T *args UNUSED) char * did_set_wlsteal(optset_T *args UNUSED) { - wayland_cb_reload(); + clip_reset_wayland(); return NULL; } diff --git a/src/option.h b/src/option.h index ce75d6862e..2963c2a05e 100644 --- a/src/option.h +++ b/src/option.h @@ -1144,7 +1144,7 @@ EXTERN long p_wmw; // 'winminwidth' EXTERN long p_wiw; // 'winwidth' #ifdef FEAT_WAYLAND EXTERN char_u *p_wse; // 'wlseat' -# ifdef FEAT_WAYLAND_CLIPBOARD +# ifdef FEAT_WAYLAND_CLIPBOARD_FS EXTERN int p_wst; // 'wlsteal' # endif EXTERN long p_wtm; // 'wltimeoutlen' diff --git a/src/optiondefs.h b/src/optiondefs.h index 2de320b9c7..fd2d5330fb 100644 --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -3009,7 +3009,7 @@ static struct vimoption options[] = #endif SCTX_INIT}, {"wlsteal", "wst", P_BOOL|P_VI_DEF, -#ifdef FEAT_WAYLAND_CLIPBOARD +#ifdef FEAT_WAYLAND_CLIPBOARD_FS (char_u *)&p_wst, PV_NONE, did_set_wlsteal, NULL, {(char_u *)FALSE, (char_u *)0L} #else diff --git a/src/optionstr.c b/src/optionstr.c index 628842ef1a..55cde57166 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -3689,7 +3689,7 @@ did_set_wlseat(optset_T *args UNUSED) #ifdef FEAT_WAYLAND_CLIPBOARD // If there isn't any seat named 'wlseat', then let the Wayland clipboard be // unavailable. Ignore errors returned. - wayland_cb_reload(); + clip_reset_wayland(); #endif return NULL; diff --git a/src/os_unix.c b/src/os_unix.c index 1094899390..73032fb547 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5731,7 +5731,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: @@ -5805,7 +5805,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 @@ -6657,6 +6657,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]; @@ -6700,11 +6703,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++; } @@ -6768,13 +6771,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 @@ -6867,14 +6866,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 @@ -6974,13 +6972,9 @@ select_eintr: socket_server_uninit(); # endif -# ifdef FEAT_WAYLAND_CLIPBOARD - // Technically we should first call wl_display_prepare_read() before - // polling the fd, then read and dispatch after we poll. However that is - // only needed for multi threaded environments to prevent deadlocks so - // we are fine. - if (ret > 0 && FD_ISSET(wayland_display_fd, &rfds)) - wayland_client_update(); +# ifdef FEAT_WAYLAND + if (wayland_fd != -1) + wayland_select_check(ret > 0 && FD_ISSET(wayland_fd, &rfds)); # endif # ifdef FEAT_XCLIPBOARD diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro index 9ccfea90a6..5727aa8b77 100644 --- a/src/proto/clipboard.pro +++ b/src/proto/clipboard.pro @@ -35,6 +35,9 @@ int clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd); int may_get_selection(int regname); void may_set_selection(void); void adjust_clip_reg(int *rp); +int clip_init_wayland(void); +void clip_uninit_wayland(void); +int clip_reset_wayland(void); char *choose_clipmethod(void); void ex_clipreset(exarg_T *eap); /* vim: set ft=c : */ diff --git a/src/proto/wayland.pro b/src/proto/wayland.pro index 990bd04588..113bf82e47 100644 --- a/src/proto/wayland.pro +++ b/src/proto/wayland.pro @@ -1,17 +1,27 @@ /* wayland.c */ -int wayland_init_client(const char *display); -void wayland_uninit_client(void); -int wayland_client_is_connected(int quiet); -int wayland_client_update(void); -int wayland_cb_init(const char *seat); -void wayland_cb_uninit(void); -garray_T * wayland_cb_get_mime_types(wayland_selection_T selection); -int wayland_cb_receive_data(const char *mime_type, wayland_selection_T selection); -int wayland_cb_own_selection( wayland_cb_send_data_func_T send_cb, wayland_cb_selection_cancelled_func_T cancelled_cb, const char **mime_types, int len, wayland_selection_T selection); -void wayland_cb_lose_selection(wayland_selection_T selection); -int wayland_cb_selection_is_owned(wayland_selection_T selection); -int wayland_cb_is_ready(void); -int wayland_cb_reload(void); -int wayland_may_restore_connection(void); +int vwl_connection_flush(vwl_connection_T *self); +int vwl_connection_dispatch(vwl_connection_T *self); +int vwl_connection_roundtrip(vwl_connection_T *self); +int wayland_init_connection(const char *display); +void wayland_uninit_connection(void); +int wayland_prepare_read(void); +int wayland_update(void); +void wayland_poll_check(int revents); +void wayland_select_check(bool is_set); void ex_wlrestore(exarg_T *eap); +vwl_seat_T *vwl_connection_get_seat(vwl_connection_T *ct, const char *label); +struct wl_keyboard *vwl_seat_get_keyboard(vwl_seat_T *seat); +vwl_data_device_manager_T *vwl_connection_get_data_device_manager(vwl_connection_T *self,wayland_selection_T req_sel, unsigned int *supported); +vwl_data_device_T *vwl_data_device_manager_get_data_device(vwl_data_device_manager_T *self,vwl_seat_T *seat); +vwl_data_source_T *vwl_data_device_manager_create_data_source(vwl_data_device_manager_T *self); +void vwl_data_device_destroy(vwl_data_device_T *self); +void vwl_data_source_destroy(vwl_data_source_T *self); +void vwl_data_offer_destroy(vwl_data_offer_T *self); +void vwl_data_device_manager_discard(vwl_data_device_manager_T *self); +void vwl_data_device_add_listener(vwl_data_device_T *self,const vwl_data_device_listener_T *listener, void *data); +void vwl_data_source_add_listener(vwl_data_source_T *self,const vwl_data_source_listener_T *listener, void *data); +void vwl_data_offer_add_listener(vwl_data_offer_T *self,const vwl_data_offer_listener_T *listener, void *data); +void vwl_data_device_set_selection(vwl_data_device_T *self, vwl_data_source_T *source, uint32_t serial, wayland_selection_T selection); +void vwl_data_source_offer(vwl_data_source_T *self, const char *mime_type); +void vwl_data_offer_receive(vwl_data_offer_T *self, const char *mime_type, int32_t fd); /* vim: set ft=c : */ diff --git a/src/structs.h b/src/structs.h index 0968b3c9a9..9fe15d978b 100644 --- a/src/structs.h +++ b/src/structs.h @@ -5237,20 +5237,26 @@ struct cellsize { #ifdef FEAT_WAYLAND +typedef struct vwl_connection_S vwl_connection_T; +typedef struct vwl_seat_S vwl_seat_T; + +# ifdef FEAT_WAYLAND_CLIPBOARD + +typedef struct vwl_data_offer_S vwl_data_offer_T; +typedef struct vwl_data_source_S vwl_data_source_T; +typedef struct vwl_data_device_S vwl_data_device_T; +typedef struct vwl_data_device_manager_S vwl_data_device_manager_T; + +typedef struct vwl_data_device_listener_S vwl_data_device_listener_T; +typedef struct vwl_data_source_listener_S vwl_data_source_listener_T; +typedef struct vwl_data_offer_listener_S vwl_data_offer_listener_T; + // Wayland selections typedef enum { - WAYLAND_SELECTION_NONE = 0x0, - WAYLAND_SELECTION_REGULAR = 0x1, - WAYLAND_SELECTION_PRIMARY = 0x2, + WAYLAND_SELECTION_NONE = 0, + WAYLAND_SELECTION_REGULAR = 1 << 0, + WAYLAND_SELECTION_PRIMARY = 1 << 1, } wayland_selection_T; -// Callback when another client wants us to send data to them -typedef void (*wayland_cb_send_data_func_T)( - const char *mime_type, - int fd, - wayland_selection_T type); - -// Callback when the selection is lost (data source object overwritten) -typedef void (*wayland_cb_selection_cancelled_func_T)(wayland_selection_T type); - -#endif // FEAT_WAYLAND +# endif +#endif diff --git a/src/testdir/test_wayland.vim b/src/testdir/test_wayland.vim index 3308b2a936..32b04e630b 100644 --- a/src/testdir/test_wayland.vim +++ b/src/testdir/test_wayland.vim @@ -21,12 +21,17 @@ let s:old_wayland_display = $WAYLAND_DISPLAY " every test function func s:PreTest() let $WAYLAND_DISPLAY=s:global_wayland_display - exe 'wlrestore ' .. $WAYLAND_DISPLAY + " Always reconnect so we have a clean state each time and clear both + " selections. + call system('wl-copy -c') + call system('wl-copy -p -c') + exe 'wlrestore! ' .. $WAYLAND_DISPLAY set cpm=wayland endfunc func s:SetupFocusStealing() + CheckFeature wayland_focus_steal if !executable('wayland-info') throw "Skipped: wayland-info program not available" endif @@ -35,7 +40,7 @@ func s:SetupFocusStealing() " seat, so we must use the user's existing Wayland session if they are in one. let $WAYLAND_DISPLAY = s:old_wayland_display - exe 'wlrestore ' .. $WAYLAND_DISPLAY + exe 'wlrestore! ' .. $WAYLAND_DISPLAY " Check if we have keyboard capability for seat if system("wayland-info -i wl_seat | grep capabilities") !~? "keyboard" @@ -50,17 +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 - if v:exception =~ 'E240:' - thrclientserverow 'Skipped: no connection to the X server' +func s:CheckClientserver() + CheckFeature clientserver + + if has('socketserver') && !has('x11') + if v:servername == "" + call remote_startserver('VIMSOCKETSERVER') endif - " ignore other errors - endtry + endif endfunc func s:EndRemoteVim(name, job) @@ -77,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 @@ -169,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 @@ -219,8 +211,6 @@ endfunc func Test_wayland_yank() call s:PreTest() - wlrestore! - new call setline(1, 'testing') @@ -265,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') @@ -360,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 @@ -376,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'}) @@ -408,24 +395,13 @@ func Test_wayland_autoselect_works() eval remote_send(l:name, "\:qa!\") - 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 @@ -449,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, "\:qa!\") - 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 @@ -489,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, "\:qa!\") - 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 @@ -528,6 +484,7 @@ endfunc " Test focus stealing func Test_wayland_focus_steal() + CheckFeature wayland_focus_steal call s:PreTest() call s:SetupFocusStealing() @@ -553,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, { @@ -577,15 +530,7 @@ func Test_wayland_bad_environment() call WaitForAssert({-> assert_equal('', \ remote_expr(l:name, 'v:wayland_display'))}) - eval remote_send(l:name, "\:qa!\") - 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 @@ -612,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') @@ -636,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 @@ -652,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') + let l:file = tempname() + let l:contents = repeat('c', 1000000) " ~ 1 MB - edit data_file_cmp + call writefile([l:contents], l:file, 'b') + call system('cat ' .. l:file .. ' | wl-copy -t TEXT') - put! + + call assert_equal(l:contents, getreg('+')) - write + call setreg('+', l:contents, 'c') - 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 feedkeys('gg0v$G"+yy', 'x') - call system('wl-paste -n -t TEXT > data_file') - - call system('cmp --silent data_file data_file_cmp') - call assert_equal(0, v:shell_error) - - bw! + call assert_equal(l:contents, system('wl-paste -n -t TEXT')) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 5603415e66..28133a07f2 100644 --- a/src/version.c +++ b/src/version.c @@ -658,6 +658,11 @@ static char *(features[]) = "+wayland_clipboard", #else "-wayland_clipboard", +#endif +#ifdef FEAT_WAYLAND_CLIPBOARD_FS + "+wayland_focus_steal", +#else + "-wayland_focus_steal", #endif "+wildignore", "+wildmenu", @@ -724,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1725, /**/ 1724, /**/ diff --git a/src/wayland.c b/src/wayland.c index e1eee19940..802f7de6d5 100644 --- a/src/wayland.c +++ b/src/wayland.c @@ -8,485 +8,204 @@ */ /* - * 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 +#include "wayland.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 capabilites 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; - -#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; /* * 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 +218,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. */ @@ -615,260 +246,11 @@ vwl_log_handler(const char *fmt, va_list args) vim_free(buf); } -/* - * 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 sucess 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. - */ - static void -vwl_disconnect_display(void) -{ - - 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; - } -} - -/* - * Tells the compositor we are still responsive. - */ - 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) -{ - // 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; - -#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; -} - -#define SET_GOBJECT(object, min_ver) \ - do { \ - chosen_interface = &object##_interface; \ - object_member = (void*)&vwl_gobjects.object; \ - min_version = min_ver; \ - } while (0) - -/* - * 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, - 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; - - 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); - - else if (STRCMP(interface, wl_data_device_manager_interface.name) == 0) - SET_GOBJECT(wl_data_device_manager, 1); - - else if (STRCMP(interface, wl_shm_interface.name) == 0) - SET_GOBJECT(wl_shm, 1); - - else if (STRCMP(interface, wl_compositor_interface.name) == 0) - SET_GOBJECT(wl_compositor, 2); - - else if (STRCMP(interface, xdg_wm_base_interface.name) == 0) - SET_GOBJECT(xdg_wm_base, 1); - - else if (STRCMP(interface, - zwp_primary_selection_device_manager_v1_interface.name) == 0) - SET_GOBJECT(zwp_primary_selection_device_manager_v1, 1); -#endif - - if (chosen_interface == NULL || version < min_version) - return; - - proxy = wl_registry_bind(vwl_display.registry.proxy, name, chosen_interface, - version); - - 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; -} - -/* - * Called when a global object is removed, if so, then do nothing. This is to - * avoid a global being removed while it is in the process of being used. Let - * the user call :wlrestore in order to reset everything. Requests to that - * global will just be ignored on the compositor side. - */ - static void -vwl_registry_listener_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 - */ - static void -vwl_add_seat(struct wl_seat *seat_proxy) -{ - vwl_seat_T *seat; - - if (ga_grow(&vwl_seats, 1) == FAIL) - return; - - seat = &((vwl_seat_T *)vwl_seats.ga_data)[vwl_seats.ga_len]; - - seat->proxy = seat_proxy; - - // Get label and capabilities - wl_seat_add_listener(seat_proxy, &vwl_seat_listener, seat); - - if (vwl_display_roundtrip(&vwl_display) == FAIL) - return; - - // Check if label has been allocated - if (seat->label == NULL) - return; - - vwl_seats.ga_len++; -} - /* * Callback for seat text label/name */ static void -vwl_seat_listener_name( +wl_seat_listener_event_name( void *data, struct wl_seat *seat_proxy UNUSED, const char *name) @@ -882,7 +264,7 @@ vwl_seat_listener_name( * Callback for seat capabilities */ static void -vwl_seat_listener_capabilities( +wl_seat_listener_event_capabilities( void *data, struct wl_seat *seat_proxy UNUSED, uint32_t capabilities) @@ -892,23 +274,300 @@ vwl_seat_listener_capabilities( seat->capabilities = capabilities; } +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 +wl_registry_listener_event_global( + void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version) +{ + vwl_connection_T *ct = data; + + if (STRCMP(interface, wl_seat_interface.name) == 0) + { + struct wl_seat *seat_proxy = wl_registry_bind(registry, name, + &wl_seat_interface, version > 5 ? 5 : version); + vwl_seat_T *seat; + + if (seat_proxy == NULL) + return; + + seat = ALLOC_CLEAR_ONE(vwl_seat_T); + + if (seat == NULL || ga_grow(&ct->gobjects.seats, 1) == FAIL) + { + vwl_seat_destroy(seat); + return; + } + + seat->proxy = seat_proxy; + wl_seat_add_listener(seat_proxy, &wl_seat_listener, seat); + + if (vwl_connection_roundtrip(ct) == FAIL || seat->label == NULL) + { + vwl_seat_destroy(seat); + 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); + + 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 + +} + +/* + * Called when a global object is removed, if so, then do nothing. This is to + * avoid a global being removed while it is in the process of being used. Let + * the user call :wlrestore in order to reset everything. Requests to that + * global will just be ignored on the compositor side. + */ + static void +wl_registry_listener_event_global_remove( + void *data UNUSED, + struct wl_registry *registry UNUSED, + uint32_t name UNUSED) +{ +} + +#ifdef FEAT_WAYLAND_CLIPBOARD_FS + static void +xdg_wm_base_listener_event_ping( + void *data UNUSED, + struct xdg_wm_base *xdg_base, + uint32_t serial) +{ + xdg_wm_base_pong(xdg_base, serial); +} +#endif + +static const struct wl_registry_listener wl_registry_listener = { + .global = wl_registry_listener_event_global, + .global_remove = wl_registry_listener_event_global_remove +}; + +#ifdef FEAT_WAYLAND_CLIPBOARD_FS +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_listener_event_ping +}; +#endif + +static void vwl_connection_destroy(vwl_connection_T *self); + +#ifdef FEAT_WAYLAND_CLIPBOARD + +# define VWL_DESTROY_GOBJECT(ct, object) \ + if (ct->gobjects.object != NULL) \ + { \ + object##_destroy(ct->gobjects.object); \ + ct->gobjects.object = NULL; \ + } + +# define VWL_GOBJECT_AVAIL(ct, object) (ct->gobjects.object != NULL) + +#endif + +// Make sure to call wayland_set_display(display); + static vwl_connection_T * +vwl_connection_new(const char *display) +{ + vwl_connection_T *ct; +#ifdef FEAT_WAYLAND_CLIPBOARD_FS + const char_u *env; + bool force_fs; +#endif + if (wayland_no_connect) + return NULL; + + // 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 + /* * Destroy/free seat. */ static void -vwl_destroy_seat(vwl_seat_T *seat) +vwl_seat_destroy(vwl_seat_T *self) { - if (seat->proxy != NULL) + if (self == NULL) + return; + if (self->proxy != NULL) { - if (wl_seat_get_version(seat->proxy) >= 5) + if (wl_seat_get_version(self->proxy) >= 5) // Helpful for the compositor - wl_seat_release(seat->proxy); + wl_seat_release(self->proxy); else - wl_seat_destroy(seat->proxy); - seat->proxy = NULL; + wl_seat_destroy(self->proxy); } - vim_free(seat->label); - seat->label = NULL; + vim_free(self->label); + vim_free(self); +} + +/* + * Disconnects the display and frees up all resources, including all global + * objects. + */ + static void +vwl_connection_destroy(vwl_connection_T *self) +{ +#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) + { + 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(self); } /* @@ -916,1567 +575,38 @@ 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); } - -/* - * 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_set_display(display); - - if (vwl_connect_display(display) == FAIL || - vwl_listen_to_registry() == FAIL) - goto fail; - - wayland_display_fd = vwl_display.fd; - - return OK; -fail: - // Set v:wayland_display to empty string (but not wayland_display_name) - wayland_set_display(""); - return FAIL; -} - -/* - * Disconnect Wayland client and free up all resources used. - */ - void -wayland_uninit_client(void) -{ -#ifdef FEAT_WAYLAND_CLIPBOARD - wayland_cb_uninit(); -#endif - vwl_disconnect_display(); - - wayland_set_display(""); -} - -/* - * Return TRUE if Wayland display connection is valid and ready. - */ - int -wayland_client_is_connected(int quiet) -{ - 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; -} - -/* - * Flush requests and process new Wayland events, does not poll the display file - * descriptor. - */ - int -wayland_client_update(void) -{ - return vwl_display_dispatch_any(&vwl_display) == -1 ? FAIL : OK; -} - -#ifdef FEAT_WAYLAND_CLIPBOARD - -/* - * 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; -} - -/* - * Configure xdg_surface - */ - static void -vwl_xdg_surface_listener_configure( - void *data UNUSED, - struct xdg_surface *surface, - uint32_t serial) -{ - xdg_surface_ack_configure(surface, serial); -} - -/* - * 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) -{ - vwl_buffer_store_T *store = data; - - store->available = TRUE; -} - -/* - * 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); - - close(store->fd); - - vim_free(store); -} - -/* - * Initialize a buffer and its backing memory pool. - */ - static vwl_buffer_store_T * -vwl_init_buffer_store(int width, int height) -{ - 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; - - 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(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); - - store->fd = fd; - - wl_buffer_add_listener(store->buffer, &vwl_cb_buffer_listener, store); - - if (vwl_display_roundtrip(&vwl_display) == -1) - { - vwl_destroy_buffer_store(store); - return NULL; - } - - store->available = TRUE; - - return store; -} - -/* - * Destroy a focus stealing store structure. - */ - 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) - { - 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); - - 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; - - 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) - struct timeval start, now; - - gettimeofday(&start, NULL); - - while (vwl_display_dispatch(&vwl_display) != -1) - { - if (store->got_focus) - break; - - gettimeofday(&now, NULL); - - if ((now.tv_sec * 1000000 + now.tv_usec) - - (start.tv_sec * 1000000 + start.tv_usec) - >= p_wtm * 1000) - goto fail; - } - } -early_exit: - vwl_destroy_fs_surface(store); - vwl_display_flush(&vwl_display); - - return OK; -fail: - vwl_destroy_fs_surface(store); - vwl_display_flush(&vwl_display); - - return FAIL; -} - -/* - * 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_fs_surface_T *store = data; - - store->got_focus = TRUE; - - if (store->on_focus != NULL) - store->on_focus(store->user_data, serial); -} - -// Dummy functions to handle keyboard events we don't care about. - - 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); -} - - static void -vwl_fs_keyboard_listener_leave( - void *data UNUSED, - struct wl_keyboard *keyboard UNUSED, - uint32_t serial UNUSED, - struct wl_surface *surface UNUSED) -{ -} - - 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) -{ -} - - 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) -{ -} - - static void -vwl_fs_keyboard_listener_repeat_info( - void *data UNUSED, - struct wl_keyboard *keyboard UNUSED, - int32_t rate UNUSED, - int32_t delay UNUSED) -{ -} - -#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) - - static void -vwl_data_device_destroy(vwl_data_device_T *device, int alloced) -{ - VWL_CODE_DATA_OBJECT_DESTROY(device); -} - - static void -vwl_data_offer_destroy(vwl_data_offer_T *offer, int alloced) -{ - VWL_CODE_DATA_OBJECT_DESTROY(offer); -} - - static void -vwl_data_source_destroy(vwl_data_source_T *source, int alloced) -{ - VWL_CODE_DATA_OBJECT_DESTROY(source); -} - - -// 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; - -// These functions handle the more complicated data_offer and selection events. - - static void -vwl_gen_data_device_listener_data_offer(void *data, void *offer_proxy) -{ - vwl_data_device_T *device = data; - - tmp_vwl_offer = alloc(sizeof(*tmp_vwl_offer)); - - if (tmp_vwl_offer != NULL) - { - tmp_vwl_offer->proxy = offer_proxy; - tmp_vwl_offer->protocol = device->protocol; - - vwl_data_device_listener.data_offer(device, tmp_vwl_offer); - } -} - - static void -vwl_gen_data_device_listener_selection( - void *data, - void *offer_proxy, - wayland_selection_T selection, - vwl_data_protocol_T protocol) -{ - 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); - } - else - { - vwl_data_device_listener.selection(data, tmp_vwl_offer, selection); - tmp_vwl_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); \ -} - -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( - zwp_primary_selection_device_v1, zwp_primary_selection_offer_v1) - -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 -}; - -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 -}; - -struct wl_data_device_listener wl_data_device_listener = { - .data_offer = wl_data_device_listener_data_offer, - .selection = wl_data_device_listener_selection, -}; - -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 -}; - -// 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 -}; - -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 -}; - -struct wl_data_source_listener wl_data_source_listener = { - .send = wl_data_source_listener_send, - .cancelled = wl_data_source_listener_cancelled -}; - -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, -}; - -// OFFERS -struct zwlr_data_control_offer_v1_listener -zwlr_data_control_offer_v1_listener = { - .offer = zwlr_data_control_offer_v1_listener_offer -}; - -struct ext_data_control_offer_v1_listener -ext_data_control_offer_v1_listener = { - .offer = ext_data_control_offer_v1_listener_offer -}; - -struct wl_data_offer_listener wl_data_offer_listener = { - .offer = wl_data_offer_listener_offer -}; - -struct zwp_primary_selection_offer_v1_listener -zwp_primary_selection_offer_v1_listener = { - .offer = zwp_primary_selection_offer_v1_listener_offer -}; - -// `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) \ - { \ - 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) - - static void -vwl_data_device_add_listener(vwl_data_device_T *device, void *data) -{ - VWL_CODE_DATA_OBJECT_ADD_LISTENER(device); -} - - static void -vwl_data_source_add_listener(vwl_data_source_T *source, void *data) -{ - VWL_CODE_DATA_OBJECT_ADD_LISTENER(source); -} - - static void -vwl_data_offer_add_listener(vwl_data_offer_T *offer, void *data) -{ - VWL_CODE_DATA_OBJECT_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. - */ - static void -vwl_data_device_set_selection( - vwl_data_device_T *device, - vwl_data_source_T *source, - uint32_t serial, - wayland_selection_T selection) -{ - if (selection == WAYLAND_SELECTION_REGULAR) - { - switch (device->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); - break; - case VWL_DATA_PROTOCOL_CORE: - wl_data_device_set_selection( - device->proxy, source->proxy, serial); - break; - default: - break; - } - } - else if (selection == WAYLAND_SELECTION_PRIMARY) - { - switch (device->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); - break; - case VWL_DATA_PROTOCOL_PRIMARY: - zwp_primary_selection_device_v1_set_selection( - device->proxy, source->proxy, serial); - break; - 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) -{ - switch (offer->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); - break; - case VWL_DATA_PROTOCOL_CORE: - wl_data_offer_receive(offer->proxy, mime_type, fd); - break; - case VWL_DATA_PROTOCOL_PRIMARY: - zwp_primary_selection_offer_v1_receive(offer->proxy, mime_type, fd); - break; - 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) -{ - switch (manager->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); - break; - case VWL_DATA_PROTOCOL_CORE: - device->proxy = wl_data_device_manager_get_data_device( - manager->proxy, seat->proxy); - break; - case VWL_DATA_PROTOCOL_PRIMARY: - device->proxy = zwp_primary_selection_device_manager_v1_get_device( - manager->proxy, seat->proxy); - break; - default: - device->protocol = VWL_DATA_PROTOCOL_NONE; - return; - } - 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 noticable 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 @@ -2510,4 +640,812 @@ exit: #endif } +/* + * 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_connection(const char *display) +{ + wayland_set_display(display); + + wayland_ct = vwl_connection_new(display); + + if (wayland_ct == NULL) + goto fail; + + return OK; +fail: + // Set v:wayland_display to empty string (but not wayland_display_name) + wayland_set_display(""); + return FAIL; +} + +/* + * Disconnect global Wayland connection and free up all resources used. + */ + void +wayland_uninit_connection(void) +{ + if (wayland_ct == NULL) + return; +#ifdef FEAT_WAYLAND_CLIPBOARD + clip_uninit_wayland(); +#endif + vwl_connection_destroy(wayland_ct); + wayland_ct = NULL; + wayland_set_display(""); +} + +static int wayland_ct_restore_count = 0; + +/* + * Attempts to restore the Wayland display connection. + */ + static void +wayland_restore_connection(void) +{ + // 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 + } +} + +/* + * Should be called before polling (select or poll) the global Wayland + * connection display fd. Returns fd on success and -1 on failure. + */ + int +wayland_prepare_read(void) +{ + if (wayland_ct == NULL) + return -1; + + 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; + } + return wayland_ct->display.fd; +} + +/* + * Catch up on any qeueued events + */ + int +wayland_update(void) +{ + if (wayland_ct == NULL) + return FAIL; + return vwl_connection_roundtrip(wayland_ct); +} + +#ifndef HAVE_SELECT + + void +wayland_poll_check(int revents) +{ + if (wayland_ct == NULL) + return; + + 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(); +} + +#else // ifdef HAVE_SELECT + + void +wayland_select_check(bool is_set) +{ + if (wayland_ct == NULL) + return; + + if (is_set) + { + if (wl_display_read_events(wayland_ct->display.proxy) != -1) + wl_display_dispatch_pending(wayland_ct->display.proxy); + else + wayland_restore_connection(); + } + else + wl_display_cancel_read(wayland_ct->display.proxy); +} + +#endif // !HAVE_SELECT + +/* + * 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_ct != NULL && + (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); + + // Will lose any selections we own + wayland_uninit_connection(); + + // Reset amount of available tries to reconnect the display to 5 + wayland_ct_restore_count = 5; + + if (wayland_init_connection(display) == OK) + { + smsg(_("restoring Wayland display %s"), wayland_display_name); + +#ifdef FEAT_WAYLAND_CLIPBOARD + did_warn_clipboard = FALSE; + clip_init_wayland(); +#endif + } + else + msg(_("failed restoring, lost connection to Wayland display")); + + vim_free(display); + + choose_clipmethod(); +} + +#ifdef FEAT_WAYLAND_CLIPBOARD + +/* + * 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. + */ + 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) + { + manager->proxy = self->gobjects.ext_data_control_manager_v1; + manager->protocol = VWL_DATA_PROTOCOL_EXT; + + *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; + + *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) + { + manager->proxy = self->gobjects.wl_data_device_manager; + manager->protocol = VWL_DATA_PROTOCOL_CORE; + + *supported |= WAYLAND_SELECTION_REGULAR; + } + + if (req_sel == WAYLAND_SELECTION_PRIMARY + && !(*supported & WAYLAND_SELECTION_PRIMARY)) + if (self->gobjects.zwp_primary_selection_device_manager_v1 != NULL) + { + manager->proxy = + self->gobjects.zwp_primary_selection_device_manager_v1; + manager->protocol = VWL_DATA_PROTOCOL_PRIMARY; + + *supported |= WAYLAND_SELECTION_PRIMARY; + } +#endif + + if (!(*supported & req_sel)) + { + vim_free(manager); + return NULL; + } + + return manager; +} + + vwl_data_device_T * +vwl_data_device_manager_get_data_device( + vwl_data_device_manager_T *self, + vwl_seat_T *seat) +{ + vwl_data_device_T *device = ALLOC_CLEAR_ONE(vwl_data_device_T); + + 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; + + return device; +} + + 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); + + 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; + + return source; +} + + 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); + + if (offer == NULL) + return NULL; + + offer->proxy = proxy; + offer->protocol = self->protocol; + offer->data = self->data; + ga_init2(&offer->mime_types, sizeof(char *), 10); + + // Try pre allocating the array, 10 mime types seems to usually be the + // maximum from experience. + ga_grow(&offer->mime_types, 10); + + 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 + +#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); \ + } + +VWL_FUNC_DATA_PROXY_DESTROY(device) +VWL_FUNC_DATA_PROXY_DESTROY(source) + + void +vwl_data_offer_destroy(vwl_data_offer_T *self) +{ + if (self == NULL) + return; + switch (self->protocol) + { + 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); +} + +/* + * 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 (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); \ + } + +// 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; \ + } + +#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_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_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 + +VWL_FUNC_DATA_DEVICE_EVENT_FINISHED(ext_data_control_device_v1) +VWL_FUNC_DATA_DEVICE_EVENT_FINISHED(zwlr_data_control_device_v1) + +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 +}; +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 +}; +#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, +}; +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 + +# 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); \ + } + +# 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); \ + } + +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 + +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 + +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 +}; +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 + +#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) \ + { \ + 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; \ + } \ + } + +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 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 + +#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) + +/* + * Set the given selection to source. If a data control protocol is being used, + * "serial" is ignored. + */ + void +vwl_data_device_set_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 (self->protocol) + { + case VWL_DATA_PROTOCOL_EXT: + 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(self->proxy, proxy, serial); + break; +#endif + default: + break; + } + } + else if (selection == WAYLAND_SELECTION_PRIMARY) + { + switch (self->protocol) + { + case VWL_DATA_PROTOCOL_EXT: + ext_data_control_device_v1_set_primary_selection( + 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( + self->proxy, proxy, serial); + break; +#endif + default: + break; + } + } +} + + void +vwl_data_source_offer(vwl_data_source_T *self, const char *mime_type) +{ + switch (self->protocol) + { + case VWL_DATA_PROTOCOL_EXT: + 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_source_offer(self->proxy, mime_type); + break; + case VWL_DATA_PROTOCOL_PRIMARY: + zwp_primary_selection_source_v1_offer(self->proxy, mime_type); + break; +#endif + default: + break; + } +} + + void +vwl_data_offer_receive( + vwl_data_offer_T *self, + const char *mime_type, + int32_t fd) +{ + switch (self->protocol) + { + case VWL_DATA_PROTOCOL_EXT: + 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: + wl_data_offer_receive(self->proxy, mime_type, fd); + break; + case VWL_DATA_PROTOCOL_PRIMARY: + zwp_primary_selection_offer_v1_receive(self->proxy, mime_type, fd); + break; +#endif + default: + break; + } +} + +#endif // FEAT_WAYLAND_CLIPBOARD + #endif // FEAT_WAYLAND diff --git a/src/wayland.h b/src/wayland.h new file mode 100644 index 0000000000..d260af4a90 --- /dev/null +++ b/src/wayland.h @@ -0,0 +1,214 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * wayland.h: Common definitions for Wayland code + */ + + +#ifdef FEAT_WAYLAND + +#include + +#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