diff --git a/Filelist b/Filelist index 3a2e066e33..e97e17ec0e 100644 --- a/Filelist +++ b/Filelist @@ -537,7 +537,6 @@ 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 e020d8852b..40e6d93eb5 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 01 +*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -13161,9 +13161,7 @@ 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 clipboard. -wayland_focus_steal Compiled with support for Wayland clipboard focus - stealing. +wayland_clipboard Compiled with support for Wayland selections/clipboard 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 2925a33e19..89ba7c2ee4 100644 --- a/runtime/doc/gui_x11.txt +++ b/runtime/doc/gui_x11.txt @@ -1,4 +1,4 @@ -*gui_x11.txt* For Vim version 9.1. Last change: 2025 Sep 01 +*gui_x11.txt* For Vim version 9.1. Last change: 2025 Sep 02 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 X11/Wayland clipboard), it will use -register 0 and output a warning: +connection to the X11 selection (or 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 23607a3ad3..20fe85b84d 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 Sep 01 +*options.txt* For Vim version 9.1. Last change: 2025 Sep 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -10195,8 +10195,7 @@ 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_focus_steal| feature is - included} + {only when the |+wayland_clipboard| 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 e988ffb578..b00bf835b4 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1542,7 +1542,6 @@ $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 16bab26932..e29fafdf72 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -1,4 +1,4 @@ -*various.txt* For Vim version 9.1. Last change: 2025 Sep 01 +*various.txt* For Vim version 9.1. Last change: 2025 Sep 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -528,8 +528,6 @@ 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 eda3e25752..ef70f67c40 100644 --- a/runtime/doc/wayland.txt +++ b/runtime/doc/wayland.txt @@ -1,4 +1,4 @@ -*wayland.txt* For Vim version 9.1. Last change: 2025 Sep 01 +*wayland.txt* For Vim version 9.1. Last change: 2025 Sep 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -105,8 +105,7 @@ 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. Additionally, Vim must be compiled with -the |+wayland_focus_steal| feature. +xdg-shell protocol must be available. 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 faa90bb121..cf743aa7a7 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 Sep 01 +" Last Change: 2025 Sep 02 " 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_focus_steal") +if has("wayland_clipboard") 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 2bebd50081..d1b08be257 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1646,9 +1646,7 @@ 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) -# 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 +RUN_UNITTESTS = 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) \ @@ -3863,9 +3861,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 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 + 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 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 \ @@ -4566,7 +4564,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 wayland.h \ + ex_cmds.h spell.h proto.h globals.h errors.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 24bbebc9fd..5c0d3d614f 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -862,7 +862,6 @@ enable_farsi enable_xim enable_fontset with_wayland -enable_wayland_focus_steal with_x enable_gui enable_gtk2_check @@ -1543,9 +1542,6 @@ 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 @@ -9275,39 +9271,13 @@ 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` @@ -9318,23 +9288,16 @@ fi 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 b96080207b..4b2e6874a9 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -32,80 +32,6 @@ #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. @@ -119,20 +45,21 @@ static const char *supported_mimes[] = { "TEXT" }; -clip_wl_T clip_wl; - -static void -clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd); +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 bool clip_wl_owner_exists(Clipboard_T *cbd); -# endif +#if defined(USE_SYSTEM) && defined(PROTO) +static int clip_wl_owner_exists(Clipboard_T *cbd); +#endif -#endif // FEAT_WAYLAND_CLIPBOARD +#endif /* * Selection stuff using Visual mode, for cutting and pasting text to other @@ -172,24 +99,6 @@ 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 @@ -2298,13 +2207,13 @@ clip_yank_selection( str_to_reg(y_ptr, type, str, len, -1, FALSE); } - 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) +/* + * 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) { char_u *p; int lnum; @@ -2335,13 +2244,11 @@ clip_convert_selection_offset( 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 - offset; i++, j++) + for (i = 0, j = 0; i < (int)*len; i++, j++) { if (y_ptr->y_array[lnum].string[j] == '\n') p[i] = NUL; @@ -2360,17 +2267,6 @@ clip_convert_selection_offset( 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". @@ -2441,594 +2337,7 @@ adjust_clip_reg(int *rp) } } -#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; -} +#if defined(FEAT_WAYLAND_CLIPBOARD) || defined(PROTO) /* * Read data from a file descriptor and write it to the given clipboard. @@ -3041,10 +2350,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; @@ -3059,19 +2368,17 @@ 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, memory is cheap - // anyways. + // 4096 bytes seems reasonable for initial buffer size if (ga_grow(&buf, 4096) == FAIL) return; start = buf.ga_data; -#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 + // Only poll before reading when we first start, then we do non-blocking + // reads and check for EAGAIN or EINTR to signal to poll again. + goto poll_data; + + while (errno = 0, TRUE) { r = read(fd, start, buf.ga_maxlen - 1 - buf.ga_len); @@ -3080,7 +2387,18 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd) else if (r < 0) { if (errno == EAGAIN || errno == EINTR) - continue; + { +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 + } break; } @@ -3154,182 +2472,182 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd) static void clip_wl_request_selection(Clipboard_T *cbd) { - 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; + wayland_selection_T selection; + garray_T *mime_types; + int len; + int fd; + const char *chosen_mime = NULL; - 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; - } + if (cbd == &clip_star) + selection = WAYLAND_SELECTION_PRIMARY; + else if (cbd == &clip_plus) + selection = WAYLAND_SELECTION_REGULAR; else -#endif + 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) { - // Dispatch any events that still queued up before checking for a data - // offer. - if (vwl_connection_roundtrip(wayland_ct) == FAIL) - goto clear; + // Selection is empty/cleared + clip_free_selection(cbd); + return; } - if (sel->offer == NULL) - goto clear; + len = ARRAY_LENGTH(supported_mimes); - 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++) + // Loop through and pick the one we want to receive from + for (int i = 0; i < len && chosen_mime == NULL; i++) { - for (int k = 0; k < mime_types_len && chosen_mime == NULL; k++) - if (STRCMP(mime_types[k], supported_mimes[i]) == 0) + for (int k = 0; k < mime_types->ga_len && chosen_mime == NULL; k++) + { + char *mime_type = ((char**)mime_types->ga_data)[k]; + + if (STRCMP(mime_type, supported_mimes[i]) == 0) chosen_mime = supported_mimes[i]; + } } + if (chosen_mime == NULL) + return; - if (chosen_mime == NULL || pipe(fds) == -1) - goto clear; + fd = wayland_cb_receive_data(chosen_mime, selection); - vwl_data_offer_receive(sel->offer, chosen_mime, fds[1]); + if (fd == -1) + return; - close(fds[1]); // Close before we read data so that when the source client - // closes their end we receive an EOF. + // Start reading the file descriptor returned + clip_wl_receive_data(cbd, chosen_mime, 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); + close(fd); } +/* + * Write data from either the clip or plus register, depending on the given + * selection, to the file descriptor that the receiving client will read from. + */ static void -vwl_data_source_listener_event_send( - void *data, - vwl_data_source_T *source UNUSED, - const char *mime_type, - int32_t fd -) +clip_wl_send_data( + const char *mime_type, + int fd, + wayland_selection_T selection) { - 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; + Clipboard_T *cbd; + long_u length; + char_u *string; + ssize_t written = 0; + size_t total = 0; + int did_vimenc = TRUE; + int did_motion_type = TRUE; + int motion_type; + int skip_len_check = FALSE; #ifndef HAVE_SELECT - struct pollfd pfd; + 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; - // 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; + // Shouldn't happen unless there is a bug. + if (!cbd->owned) + return; + // Get the current selection clip_get_selection(cbd); - motion_type = clip_convert_selection_offset(&string, &length, offset, cbd); + motion_type = clip_convert_selection(&string, &length, cbd); if (motion_type < 0) goto exit; - if (is_vimenc) + if (STRCMP(mime_type, VIMENC_ATOM_NAME) == 0) { - string[0] = (char_u)motion_type; - // strcpy copies the NUL terminator too - strcpy((char *)string + 1, (char *)p_enc); + did_vimenc = FALSE; + did_motion_type = FALSE; } - else if (is_vim) - string[0] = (char_u)motion_type; + else if (STRCMP(mime_type, VIM_ATOM_NAME) == 0) + did_motion_type = FALSE; - - while (total < (size_t)length && + while ((total < (size_t)length || skip_len_check) && #ifndef HAVE_SELECT - poll(&pfd, 1, p_wtm) > 0) + poll(&pfd, 1, p_wtm) > 0) #else - ((tv.tv_sec = p_wtm / 1000, tv.tv_usec = (p_wtm % 1000) * 1000), - select(fd + 1, NULL, &wfds, NULL, &tv) > 0)) + select(fd + 1, NULL, &wfds, NULL, &tv) > 0) #endif { - ssize_t w = write(fd, string + total, length - total); + // First byte sent is motion type for vim specific formats + if (!did_motion_type) + { + if (total == 1) + { + total = 0; + did_motion_type = TRUE; + continue; + } + // We cast to char so that we only send one byte + written = write( fd, (char_u*)&motion_type, 1); + skip_len_check = TRUE; + } + else if (!did_vimenc) + { + // For the vimenc format, after the first byte is the encoding type, + // which is null terminated. Make sure we write that before writing + // the actual selection. + if (total == STRLEN(p_enc) + 1) + { + total = 0; + did_vimenc = TRUE; + continue; + } + // Include null terminator + written = write(fd, p_enc + total, STRLEN(p_enc) + 1 - total); + skip_len_check = TRUE; + } + else + { + // write the actual selection to the fd + written = write(fd, string + total, length - total); + if (skip_len_check) + skip_len_check = FALSE; + } - if (w == -1) - break; - total += w; + if (written == -1) + break; + total += written; + +#ifdef HAVE_SELECT + tv.tv_sec = 0; + tv.tv_usec = p_wtm * 1000; +#endif } - - vim_free(string); exit: - close(fd); + vim_free(string); } +/* + * Called if another client gains ownership of the given selection. If so then + * lose the selection internally. + */ static void -vwl_data_source_listener_event_cancelled( - void *data, - vwl_data_source_T *source UNUSED) +clip_wl_selection_cancelled(wayland_selection_T selection) { - 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); + if (selection == WAYLAND_SELECTION_REGULAR) + clip_lose_selection(&clip_plus); + else if (selection == WAYLAND_SELECTION_PRIMARY) + clip_lose_selection(&clip_star); } /* @@ -3340,81 +2658,36 @@ clip_wl_do_set_selection(void *data, uint32_t serial) static int clip_wl_own_selection(Clipboard_T *cbd) { - clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd); - wayland_selection_T sel_type = clip_wl_get_selection_type(sel); + wayland_selection_T selection; - if (!sel->available || vwl_connection_roundtrip(wayland_ct) == FAIL) + if (cbd == &clip_star) + selection = WAYLAND_SELECTION_PRIMARY; + else if (cbd == &clip_plus) + selection = WAYLAND_SELECTION_REGULAR; + else return FAIL; - 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; + return wayland_cb_own_selection( + clip_wl_send_data, + clip_wl_selection_cancelled, + supported_mimes, + sizeof(supported_mimes)/sizeof(*supported_mimes), + selection); } /* - * Disown the selection that cbd corresponds to. + * Disown the selection that cbd corresponds to. Note that the the cancelled + * event is not sent when the data source is destroyed. */ static void clip_wl_lose_selection(Clipboard_T *cbd) { - clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd); + if (cbd == &clip_plus) + wayland_cb_lose_selection(WAYLAND_SELECTION_REGULAR); + else if (cbd == &clip_star) + wayland_cb_lose_selection(WAYLAND_SELECTION_PRIMARY); - if (!sel->available) - return; - - vwl_data_source_destroy(sel->source); - sel->source = NULL; + /* wayland_cb_lose_selection(selection); */ } /* @@ -3428,18 +2701,15 @@ clip_wl_set_selection(Clipboard_T *cbd UNUSED) #if defined(USE_SYSTEM) && defined(PROTO) /* - * Return true if we own the selection corresponding to cbd or another client - * does. + * Return TRUE if we own the selection corresponding to cbd */ - static bool + static int clip_wl_owner_exists(Clipboard_T *cbd) { - 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); + if (cbd == &clip_plus) + return wayland_cb_selection_is_owned(WAYLAND_SELECTION_REGULAR); + else if (cbd == &clip_star) + return wayland_cb_selection_is_owned(WAYLAND_SELECTION_PRIMARY); } #endif @@ -3475,7 +2745,7 @@ get_clipmethod(char_u *str) #endif { #ifdef FEAT_WAYLAND_CLIPBOARD - if (clip_wl.regular.available || clip_wl.primary.available) + if (wayland_cb_is_ready()) method = CLIPMETHOD_WAYLAND; #endif } @@ -3573,7 +2843,7 @@ choose_clipmethod(void) if (method == CLIPMETHOD_GUI) // We only interact with Wayland for the clipboard, we can just deinit // everything. - wayland_uninit_connection(); + wayland_uninit_client(); #endif // Deinitialize clipboard if there is no way to access clipboard diff --git a/src/config.h.in b/src/config.h.in index 983f186b8d..e8775d98c3 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -12,9 +12,6 @@ /* 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 c00c063eea..983c582247 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -2464,25 +2464,11 @@ 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` @@ -2492,20 +2478,15 @@ if test "$with_wayland" = yes; then 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 c788a2bb65..3a0e9a77af 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -7615,13 +7615,6 @@ 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 d7db96cd57..826adbe2bd 100644 --- a/src/feature.h +++ b/src/feature.h @@ -928,13 +928,6 @@ # 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 022176d904..8d031028ea 100644 --- a/src/globals.h +++ b/src/globals.h @@ -2080,18 +2080,15 @@ EXTERN clipmethod_T clipmethod INIT(= CLIPMETHOD_NONE); #ifdef FEAT_WAYLAND -// Wayland display name for global connection (ex. wayland-0). Can be NULL -EXTERN char *wayland_display_name INIT(= NULL); - -// 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); +// Wayland display name (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; + #endif #if defined(FEAT_CLIENTSERVER) && !defined(MSWIN) diff --git a/src/main.c b/src/main.c index eddfb9e3dd..942a7e5f8f 100644 --- a/src/main.c +++ b/src/main.c @@ -682,18 +682,15 @@ vim_main2(void) if (!gui.in_use) # endif { - sprintf(wayland_vim_special_mime, "application/x-vim-instance-%ld", - mch_get_pid()); - - if (wayland_init_connection(wayland_display_name) == OK) + if (wayland_init_client(wayland_display_name) == OK) { TIME_MSG("connected to Wayland display"); # ifdef FEAT_WAYLAND_CLIPBOARD - if (clip_init_wayland() == OK) + if (wayland_cb_init((char*)p_wse) == OK) TIME_MSG("setup Wayland clipboard"); -# endif } +# endif } #endif diff --git a/src/option.c b/src/option.c index 99083d0d3a..f258836ec4 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) { - clip_reset_wayland(); + wayland_cb_reload(); return NULL; } diff --git a/src/option.h b/src/option.h index 2963c2a05e..ce75d6862e 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_FS +# ifdef FEAT_WAYLAND_CLIPBOARD EXTERN int p_wst; // 'wlsteal' # endif EXTERN long p_wtm; // 'wltimeoutlen' diff --git a/src/optiondefs.h b/src/optiondefs.h index fd2d5330fb..2de320b9c7 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_FS +#ifdef FEAT_WAYLAND_CLIPBOARD (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 55cde57166..628842ef1a 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. - clip_reset_wayland(); + wayland_cb_reload(); #endif return NULL; diff --git a/src/os_unix.c b/src/os_unix.c index 73032fb547..1094899390 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_update(); + wayland_client_update(); #endif } finished: @@ -5805,7 +5805,7 @@ finished: #ifdef FEAT_WAYLAND // Handle Wayland events such as sending data as the source // client. - wayland_update(); + wayland_client_update(); #endif // Wait for 1 to 10 msec. 1 is faster but gives the child @@ -6657,9 +6657,6 @@ 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]; @@ -6703,11 +6700,11 @@ RealWaitForChar(int fd, long msec, int *check_for_gpm UNUSED, int *interrupted) } # endif -# ifdef FEAT_WAYLAND - if ((wayland_fd = wayland_prepare_read()) >= 0) +# ifdef FEAT_WAYLAND_CLIPBOARD + if (wayland_may_restore_connection()) { wayland_idx = nfd; - fds[nfd].fd = wayland_fd; + fds[nfd].fd = wayland_display_fd; fds[nfd].events = POLLIN; nfd++; } @@ -6771,9 +6768,13 @@ RealWaitForChar(int fd, long msec, int *check_for_gpm UNUSED, int *interrupted) } # endif -# ifdef FEAT_WAYLAND - if (wayland_idx >= 0) - wayland_poll_check(fds[wayland_idx].revents); +# 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(); # endif # ifdef FEAT_XCLIPBOARD @@ -6866,13 +6867,14 @@ select_eintr: } # endif -# ifdef FEAT_WAYLAND - if ((wayland_fd = wayland_prepare_read()) >= 0) - { - FD_SET(wayland_fd, &rfds); +# ifdef FEAT_WAYLAND_CLIPBOARD - if (maxfd < wayland_fd) - maxfd = wayland_fd; + if (wayland_may_restore_connection()) + { + FD_SET(wayland_display_fd, &rfds); + + if (maxfd < wayland_display_fd) + maxfd = wayland_display_fd; } # endif @@ -6972,9 +6974,13 @@ select_eintr: socket_server_uninit(); # endif -# ifdef FEAT_WAYLAND - if (wayland_fd != -1) - wayland_select_check(ret > 0 && FD_ISSET(wayland_fd, &rfds)); +# 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(); # endif # ifdef FEAT_XCLIPBOARD diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro index 5727aa8b77..9ccfea90a6 100644 --- a/src/proto/clipboard.pro +++ b/src/proto/clipboard.pro @@ -35,9 +35,6 @@ 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 113bf82e47..990bd04588 100644 --- a/src/proto/wayland.pro +++ b/src/proto/wayland.pro @@ -1,27 +1,17 @@ /* wayland.c */ -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); +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); 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 9fe15d978b..0968b3c9a9 100644 --- a/src/structs.h +++ b/src/structs.h @@ -5237,26 +5237,20 @@ 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 = 0, - WAYLAND_SELECTION_REGULAR = 1 << 0, - WAYLAND_SELECTION_PRIMARY = 1 << 1, + WAYLAND_SELECTION_NONE = 0x0, + WAYLAND_SELECTION_REGULAR = 0x1, + WAYLAND_SELECTION_PRIMARY = 0x2, } wayland_selection_T; -# endif -#endif +// 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 diff --git a/src/testdir/test_wayland.vim b/src/testdir/test_wayland.vim index 32b04e630b..3308b2a936 100644 --- a/src/testdir/test_wayland.vim +++ b/src/testdir/test_wayland.vim @@ -21,17 +21,12 @@ let s:old_wayland_display = $WAYLAND_DISPLAY " every test function func s:PreTest() let $WAYLAND_DISPLAY=s:global_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 + 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 @@ -40,7 +35,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" @@ -55,14 +50,17 @@ func s:UnsetupFocusStealing() unlet $VIM_WAYLAND_FORCE_FS endfunc -func s:CheckClientserver() - CheckFeature clientserver - - if has('socketserver') && !has('x11') - if v:servername == "" - call remote_startserver('VIMSOCKETSERVER') +" 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' endif - endif + " ignore other errors + endtry endfunc func s:EndRemoteVim(name, job) @@ -79,7 +77,11 @@ endfunc func Test_wayland_startup() call s:PreTest() - call s:CheckClientserver() + call s:CheckXConnection() + + if v:servername == "" + call remote_startserver('VIMSOCKETSERVER') + endif let l:name = 'WLVIMTEST' let l:cmd = GetVimCommand() .. ' --servername ' .. l:name @@ -167,12 +169,18 @@ 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 @@ -211,6 +219,8 @@ endfunc func Test_wayland_yank() call s:PreTest() + wlrestore! + new call setline(1, 'testing') @@ -255,8 +265,7 @@ func Test_wayland_mime_types_correct() \ 'text/plain', \ 'UTF8_STRING', \ 'STRING', - \ 'TEXT', - \ 'application/x-vim-instance-' .. getpid() + \ 'TEXT' \ ] call setreg('+', 'text', 'c') @@ -351,7 +360,7 @@ endfunc " Test if autoselect option in 'clipboard' works properly for Wayland func Test_wayland_autoselect_works() call s:PreTest() - call s:CheckClientserver() + call s:CheckXConnection() let l:lines =<< trim END set cpm=wayland @@ -367,6 +376,10 @@ 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'}) @@ -395,13 +408,24 @@ func Test_wayland_autoselect_works() eval remote_send(l:name, "\:qa!\") - call s:EndRemoteVim(l:name, l:job) + 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 endfunc " Check if the -Y flag works properly func Test_no_wayland_connect_cmd_flag() call s:PreTest() - call s:CheckClientserver() + call s:CheckXConnection() + + if v:servername == "" + call remote_startserver('VIMSOCKETSERVER') + endif let l:name = 'WLFLAGVIMTEST' let l:cmd = GetVimCommand() .. ' -Y --servername ' .. l:name @@ -425,13 +449,25 @@ func Test_no_wayland_connect_cmd_flag() call WaitForAssert({-> assert_equal('', \ remote_expr(l:name, 'v:wayland_display'))}) - call s:EndRemoteVim(l:name, l:job) + 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 endfunc " Test behaviour when we do something like suspend Vim func Test_wayland_become_inactive() call s:PreTest() - call s:CheckClientserver() + call s:CheckXConnection() + + if v:servername == "" + call remote_startserver('VIMSOCKETSERVER') + endif let l:name = 'WLLOSEVIMTEST' let l:cmd = GetVimCommand() .. ' --servername ' .. l:name @@ -453,7 +489,15 @@ func Test_wayland_become_inactive() call WaitForAssert({-> assert_equal("Nothing is copied\n", \ system('wl-paste -n'))}) - call s:EndRemoteVim(l:name, l:job) + 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 endfunc " Test wlseat option @@ -484,7 +528,6 @@ endfunc " Test focus stealing func Test_wayland_focus_steal() - CheckFeature wayland_focus_steal call s:PreTest() call s:SetupFocusStealing() @@ -510,13 +553,17 @@ endfunc " Test when environment is not suitable for Wayland func Test_wayland_bad_environment() call s:PreTest() - call s:CheckClientserver() + call s:CheckXConnection() 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, { @@ -530,7 +577,15 @@ func Test_wayland_bad_environment() call WaitForAssert({-> assert_equal('', \ remote_expr(l:name, 'v:wayland_display'))}) - call s:EndRemoteVim(l:name, l:job) + 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 let $XDG_RUNTIME_DIR = l:old endfunc @@ -557,11 +612,7 @@ func Test_wayland_lost_selection() call assert_equal('regular', getreg('+')) call assert_equal('primary', getreg('*')) -endfunc - -" Same as above but for the focus stealing method -func Test_wayland_lost_selection_focus_steal() - call s:PreTest() + " Test focus stealing call s:SetupFocusStealing() call setreg('+', 'regular') @@ -585,14 +636,14 @@ func Test_wayland_lost_selection_focus_steal() call s:UnsetupFocusStealing() endfunc -" Test when there are no supported mime types for the selection +" Test when there are no supported mime types for the selecftion func Test_wayland_no_mime_types_supported() call s:PreTest() - call system('wl-copy tester') - call assert_equal('tester', getreg('+')) + wlrestore! call system('wl-copy -t image/png testing') + call assert_equal('', getreg('+')) call assert_fails('put +', 'E353:') endfunc @@ -601,17 +652,28 @@ endfunc func Test_wayland_handle_large_data() call s:PreTest() - let l:file = tempname() - let l:contents = repeat('c', 1000000) " ~ 1 MB + 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') - call writefile([l:contents], l:file, 'b') - call system('cat ' .. l:file .. ' | wl-copy -t TEXT') + edit data_file_cmp - call assert_equal(l:contents, getreg('+')) + put! + - call setreg('+', l:contents, 'c') + write - call assert_equal(l:contents, system('wl-paste -n -t TEXT')) + 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! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 28133a07f2..ffa7bf10e4 100644 --- a/src/version.c +++ b/src/version.c @@ -658,11 +658,6 @@ static char *(features[]) = "+wayland_clipboard", #else "-wayland_clipboard", -#endif -#ifdef FEAT_WAYLAND_CLIPBOARD_FS - "+wayland_focus_steal", -#else - "-wayland_focus_steal", #endif "+wildignore", "+wildmenu", @@ -729,6 +724,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1726, /**/ 1725, /**/ diff --git a/src/wayland.c b/src/wayland.c index 802f7de6d5..e1eee19940 100644 --- a/src/wayland.c +++ b/src/wayland.c @@ -8,204 +8,485 @@ */ /* - * 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 + * wayland.c: Stuff related to Wayland */ #include "vim.h" #ifdef FEAT_WAYLAND -#include "wayland.h" +#include -vwl_connection_T *wayland_ct; +#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 /* * 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. */ - int -vwl_connection_flush(vwl_connection_T *self) + static int +vwl_display_flush(vwl_display_T *display) { + int ret; + #ifndef HAVE_SELECT struct pollfd fds; - fds.fd = self->display.fd; - fds.events = POLLOUT; + fds.fd = display->fd; + fds.events = POLLOUT; #else fd_set wfds; struct timeval tv; FD_ZERO(&wfds); - FD_SET(self->display.fd, &wfds); + FD_SET(display->fd, &wfds); + + tv.tv_sec = p_wtm / 1000; + tv.tv_usec = (p_wtm % 1000) * 1000; #endif - if (self->display.proxy == NULL) + if (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 (true) + while (errno = 0, (ret = wl_display_flush(display->proxy)) == -1 + && errno == EAGAIN) { - 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 - 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; + if (select(display->fd + 1, NULL, &wfds, NULL, &tv) <= 0) +#endif + 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_event_done(void *data, struct wl_callback *callback, - uint32_t callback_data UNUSED) +vwl_callback_done(void *data, struct wl_callback *callback, + uint32_t cb_data UNUSED) { - *((bool*)data) = true; + *((int*)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. */ - int -vwl_connection_roundtrip(vwl_connection_T *self) + static int +vwl_display_roundtrip(vwl_display_T *display) { struct wl_callback *callback; - int ret; - bool done = false; -#ifdef ELAPSED_FUNC - elapsed_T start_tv; -#endif + int ret, done = FALSE; + struct timeval start, now; - if (self->display.proxy == NULL) + if (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(self->display.proxy); + callback = wl_display_sync(display->proxy); if (callback == NULL) return FAIL; wl_callback_add_listener(callback, &vwl_callback_listener, &done); -#ifdef ELAPSED_FUNC - ELAPSED_INIT(start_tv); -#endif + gettimeofday(&start, NULL); // Wait till we get the done event (which will set `done` to TRUE), unless // we timeout - while (true) + while (TRUE) { - ret = vwl_connection_dispatch(self); + ret = vwl_display_dispatch(display); if (done || ret == -1) break; -#ifdef ELAPSED_FUNC - if (ELAPSED_FUNC(start_tv) >= p_wtm) + gettimeofday(&now, NULL); + + if ((now.tv_sec * 1000000 + now.tv_usec) - + (start.tv_sec * 1000000 + start.tv_usec) >= p_wtm * 1000) { ret = -1; break; } -#endif } if (ret == -1) @@ -218,6 +499,94 @@ vwl_connection_roundtrip(vwl_connection_T *self) 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. */ @@ -246,11 +615,260 @@ 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 -wl_seat_listener_event_name( +vwl_seat_listener_name( void *data, struct wl_seat *seat_proxy UNUSED, const char *name) @@ -264,7 +882,7 @@ wl_seat_listener_event_name( * Callback for seat capabilities */ static void -wl_seat_listener_event_capabilities( +vwl_seat_listener_capabilities( void *data, struct wl_seat *seat_proxy UNUSED, uint32_t capabilities) @@ -274,300 +892,23 @@ wl_seat_listener_event_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_seat_destroy(vwl_seat_T *self) +vwl_destroy_seat(vwl_seat_T *seat) { - if (self == NULL) - return; - if (self->proxy != NULL) + if (seat->proxy != NULL) { - if (wl_seat_get_version(self->proxy) >= 5) + if (wl_seat_get_version(seat->proxy) >= 5) // Helpful for the compositor - wl_seat_release(self->proxy); + wl_seat_release(seat->proxy); else - wl_seat_destroy(self->proxy); + wl_seat_destroy(seat->proxy); + seat->proxy = 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); + vim_free(seat->label); + seat->label = NULL; } /* @@ -575,38 +916,1567 @@ vwl_connection_destroy(vwl_connection_T *self) * If NULL or an empty string is passed as the label then the first available * seat found is used. */ - vwl_seat_T * -vwl_connection_get_seat(vwl_connection_T *self, const char *label) + static vwl_seat_T * +vwl_get_seat(const char *label) { - if ((STRCMP(label, "") == 0 || label == NULL) - && self->gobjects.seats.ga_len > 0) - return ((vwl_seat_T **)self->gobjects.seats.ga_data)[0]; + if ((STRCMP(label, "") == 0 || label == NULL) && vwl_seats.ga_len > 0) + return &((vwl_seat_T *)vwl_seats.ga_data)[0]; - for (int i = 0; i < self->gobjects.seats.ga_len; i++) + for (int i = 0; i < vwl_seats.ga_len; i++) { - vwl_seat_T *seat = ((vwl_seat_T **)self->gobjects.seats.ga_data)[i]; + vwl_seat_T *seat = &((vwl_seat_T *)vwl_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. */ - struct wl_keyboard * -vwl_seat_get_keyboard(vwl_seat_T *self) + static struct wl_keyboard * +vwl_seat_get_keyboard(vwl_seat_T *seat) { - if (!(self->capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) + if (!(seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) return NULL; - return wl_seat_get_keyboard(self->proxy); + return wl_seat_get_keyboard(seat->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 @@ -640,812 +2510,4 @@ 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 deleted file mode 100644 index d260af4a90..0000000000 --- a/src/wayland.h +++ /dev/null @@ -1,214 +0,0 @@ -/* 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