/* 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.c: Stuff related to Wayland
 */

#include "vim.h"

#ifdef FEAT_WAYLAND

#include <wayland-client.h>

#ifdef FEAT_WAYLAND_CLIPBOARD
# include "auto/wayland/wlr-data-control-unstable-v1.h"
# include "auto/wayland/ext-data-control-v1.h"
# include "auto/wayland/xdg-shell.h"
# include "auto/wayland/primary-selection-unstable-v1.h"
#endif

// Struct that represents a seat. (Should be accessed via
// vwl_get_seat()).
typedef struct {
    struct wl_seat  *proxy;
    char	    *label;	    // Name of seat as text (e.g. seat0,
				    // seat1...).
    uint32_t	    capabilities;   // Bitmask of the 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.
 */
    static int
vwl_display_flush(vwl_display_T *display)
{
    int ret;

#ifndef HAVE_SELECT
    struct pollfd fds;

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

    FD_ZERO(&wfds);
    FD_SET(display->fd, &wfds);

    tv.tv_sec	= p_wtm / 1000;
    tv.tv_usec	= (p_wtm % 1000) * 1000;
#endif

    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 (errno = 0, (ret = wl_display_flush(display->proxy)) == -1
	    && errno == EAGAIN)
    {
#ifndef HAVE_SELECT
	if (poll(&fds, 1, p_wtm) <= 0)
#else
	    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
    }
    // Return FAIL on error or timeout
    if ((errno != 0 && errno != EAGAIN) || ret == -1)
	return FAIL;

    return OK;
}

/*
 * Called when compositor is done processing requests/events.
 */
    static void
vwl_callback_done(void *data, struct wl_callback *callback,
	uint32_t cb_data UNUSED)
{
    *((int*)data) = TRUE;
    wl_callback_destroy(callback);
}

/*
 * Like wl_display_roundtrip but polls the display fd with a timeout. Returns
 * FAIL on failure and OK on success.
 */
    static int
vwl_display_roundtrip(vwl_display_T *display)
{
    struct wl_callback	*callback;
    int			ret, done = FALSE;
    struct timeval start, now;

    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(display->proxy);

    if (callback == NULL)
	return FAIL;

    wl_callback_add_listener(callback, &vwl_callback_listener, &done);

    gettimeofday(&start, NULL);

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

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

	gettimeofday(&now, NULL);

	if ((now.tv_sec * 1000000 + now.tv_usec) -
		(start.tv_sec * 1000000 + start.tv_usec) >= p_wtm * 1000)
	{
	    ret = -1;
	    break;
	}
    }

    if (ret == -1)
    {
	if (!done)
	    wl_callback_destroy(callback);
	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.
 */
    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.
 */
    static void
vwl_log_handler(const char *fmt, va_list args)
{
    // 512 bytes should be big enough
    char	*buf	= alloc(512);
    char	*prefix = _("wayland protocol error -> ");
    size_t	len	= STRLEN(prefix);

    if (buf == NULL)
	return;

    vim_strncpy((char_u*)buf, (char_u*)prefix, len);
    vim_vsnprintf(buf + len, 4096 - len, fmt, args);

    // Remove newline that libwayland puts
    buf[STRLEN(buf) - 1] = NUL;

#ifdef FEAT_EVAL
    ch_log(NULL, "%s", buf);
#endif
    emsg(buf);

    vim_free(buf);
}

/*
 * Connect to the display with name; passing NULL will use libwayland's way of
 * getting the display. Additionally get the registry object but will not
 * starting listening. Returns OK on sucess and FAIL on failure.
 */
    static int
vwl_connect_display(const char *display)
{
    if (wayland_no_connect)
	return FAIL;

    // We will get an error if XDG_RUNTIME_DIR is not set.
    if (mch_getenv("XDG_RUNTIME_DIR") == NULL)
	return FAIL;

    // Must set log handler before we connect display in order to work.
    wl_log_set_handler_client(vwl_log_handler);

    vwl_display.proxy = wl_display_connect(display);

    if (vwl_display.proxy == NULL)
	return FAIL;

    wayland_set_display(display);
    vwl_display.fd = wl_display_get_fd(vwl_display.proxy);

    vwl_display.registry.proxy = wl_display_get_registry(vwl_display.proxy);

    if (vwl_display.registry.proxy == NULL)
    {
	vwl_disconnect_display();
	return FAIL;
    }

    return OK;
}

#define destroy_gobject(object) \
    if (vwl_gobjects.object != NULL) \
    { \
	object##_destroy(vwl_gobjects.object); \
	vwl_gobjects.object = NULL; \
    }

/*
 * Disconnects the display and frees up all resources, including all global
 * objects.
 */
    static void
vwl_disconnect_display(void)
{

    destroy_gobject(ext_data_control_manager_v1)
    destroy_gobject(zwlr_data_control_manager_v1)
    destroy_gobject(wl_data_device_manager)
    destroy_gobject(wl_shm)
    destroy_gobject(wl_compositor)
    destroy_gobject(xdg_wm_base)
    destroy_gobject(zwp_primary_selection_device_manager_v1)

    for (int i = 0; i < vwl_seats.ga_len; i++)
	vwl_destroy_seat(&((vwl_seat_T *)vwl_seats.ga_data)[i]);
    ga_clear(&vwl_seats);
    vwl_seats.ga_len = 0;

    if (vwl_display.registry.proxy != NULL)
    {
	wl_registry_destroy(vwl_display.registry.proxy);
	vwl_display.registry.proxy = NULL;
    }
    if (vwl_display.proxy != NULL)
    {
	wl_display_disconnect(vwl_display.proxy);
	vwl_display.proxy = NULL;
    }
}

/*
 * Tells the compositor we are still responsive.
 */
    static void
vwl_xdg_wm_base_listener_ping(
	void *data UNUSED,
	struct xdg_wm_base *base,
	uint32_t serial)
{
    xdg_wm_base_pong(base, serial);
}

/*
 * Start listening to the registry and get initial set of global
 * objects/interfaces.
 */
    static int
vwl_listen_to_registry(void)
{
    // Only meant for debugging/testing purposes
    char_u *env = mch_getenv("VIM_WAYLAND_FORCE_FS");

    if (env != NULL && STRCMP(env, "1") == 0)
	force_fs = TRUE;
    else
	force_fs = FALSE;

    ga_init2(&vwl_seats, sizeof(vwl_seat_T), 1);

    wl_registry_add_listener(
	    vwl_display.registry.proxy,
	    &vwl_registry_listener,
	    NULL);

    if (vwl_display_roundtrip(&vwl_display) == FAIL)
	return FAIL;

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

#define SET_GOBJECT(object, min_ver) \
    do { \
	chosen_interface = &object##_interface; \
	object_member = (void*)&vwl_gobjects.object; \
	min_version = min_ver; \
    } while (0)

/*
 * Callback for global event, for each global interface the compositor supports.
 * Keep in sync with vwl_disconnect_display().
 */
    static void
vwl_registry_listener_global(
	void		    *data UNUSED,
	struct wl_registry  *registry UNUSED,
	uint32_t	    name,
	const char	    *interface,
	uint32_t	    version)
{

    const struct wl_interface	*chosen_interface = NULL;
    void			*proxy;
    uint32_t			min_version;
    void			**object_member;

    if (STRCMP(interface, wl_seat_interface.name) == 0)
    {
	chosen_interface = &wl_seat_interface;
	min_version = 2;
    }
#ifdef FEAT_WAYLAND_CLIPBOARD
    else if (STRCMP(interface, zwlr_data_control_manager_v1_interface.name) == 0)
	SET_GOBJECT(zwlr_data_control_manager_v1, 1);

    else if (STRCMP(interface, ext_data_control_manager_v1_interface.name) == 0)
	SET_GOBJECT(ext_data_control_manager_v1, 1);

    else if (STRCMP(interface, wl_data_device_manager_interface.name) == 0)
	SET_GOBJECT(wl_data_device_manager, 1);

    else if (STRCMP(interface, wl_shm_interface.name) == 0)
	SET_GOBJECT(wl_shm, 1);

    else if (STRCMP(interface, wl_compositor_interface.name) == 0)
	SET_GOBJECT(wl_compositor, 2);

    else if (STRCMP(interface, xdg_wm_base_interface.name) == 0)
	SET_GOBJECT(xdg_wm_base, 1);

    else if (STRCMP(interface,
		zwp_primary_selection_device_manager_v1_interface.name) == 0)
	SET_GOBJECT(zwp_primary_selection_device_manager_v1, 1);
#endif

    if (chosen_interface == NULL || version < min_version)
	return;

    proxy = wl_registry_bind(vwl_display.registry.proxy, name, chosen_interface,
	    version);

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

/*
 * Called when a global object is removed, if so, then do nothing. This is to
 * avoid a global being removed while it is in the process of being used. Let
 * the user call :wlrestore in order to reset everything. Requests to that
 * global will just be ignored on the compositor side.
 */
    static void
vwl_registry_listener_global_remove(
	void		    *data UNUSED,
	struct wl_registry  *registry UNUSED,
	uint32_t	    name UNUSED)
{
}

/*
 * Add a new seat given its proxy to the global grow array
 */
    static void
vwl_add_seat(struct wl_seat *seat_proxy)
{
    vwl_seat_T *seat;

    if (ga_grow(&vwl_seats, 1) == FAIL)
	return;

    seat = &((vwl_seat_T *)vwl_seats.ga_data)[vwl_seats.ga_len];

    seat->proxy = seat_proxy;

    // Get label and capabilities
    wl_seat_add_listener(seat_proxy, &vwl_seat_listener, seat);

    if (vwl_display_roundtrip(&vwl_display) == FAIL)
	return;

    // Check if label has been allocated
    if (seat->label == NULL)
	return;

    vwl_seats.ga_len++;
}

/*
 * Callback for seat text label/name
 */
    static void
vwl_seat_listener_name(
	void		*data,
	struct wl_seat	*seat_proxy UNUSED,
	const char	*name)
{
    vwl_seat_T *seat = data;

    seat->label = (char *)vim_strsave((char_u *)name);
}

/*
 * Callback for seat capabilities
 */
    static void
vwl_seat_listener_capabilities(
	void		*data,
	struct wl_seat	*seat_proxy UNUSED,
	uint32_t	capabilities)
{
    vwl_seat_T *seat = data;

    seat->capabilities = capabilities;
}

/*
 * Destroy/free seat.
 */
    static void
vwl_destroy_seat(vwl_seat_T *seat)
{
    if (seat->proxy != NULL)
    {
	if (wl_seat_get_version(seat->proxy) >= 5)
	    // Helpful for the compositor
	    wl_seat_release(seat->proxy);
	else
	    wl_seat_destroy(seat->proxy);
	seat->proxy = NULL;
    }
    vim_free(seat->label);
    seat->label = NULL;
}

/*
 * Return a seat with the give name/label. If none exists then NULL is returned.
 * If NULL or an empty string is passed as the label then the first available
 * seat found is used.
 */
    static vwl_seat_T *
vwl_get_seat(const char *label)
{
    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 < vwl_seats.ga_len; i++)
    {
	vwl_seat_T *seat = &((vwl_seat_T *)vwl_seats.ga_data)[i];
	if (STRCMP(seat->label, label) == 0)
	    return seat;
    }
    return NULL;
}

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

    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
 * string, unless NULL is passed. If NULL is passed then v:wayland_display is
 * set to $WAYLAND_DISPLAY, but wayland_display_name is set to NULL.
 */
    static void
wayland_set_display(const char *display)
{
    if (display == NULL)
	display = (char*)mch_getenv((char_u*)"WAYLAND_DISPLAY");
    else if (display == wayland_display_name)
	// Don't want to be freeing vwl_display_strname then trying to copy it
	// after.
	goto exit;

    if (display == NULL)
	// $WAYLAND_DISPLAY is not set
	display = "";

    // Leave unchanged if display is empty (but not NULL)
    if (STRCMP(display, "") != 0)
    {
	vim_free(wayland_display_name);
	wayland_display_name = (char*)vim_strsave((char_u*)display);
    }

exit:
#ifdef FEAT_EVAL
    set_vim_var_string(VV_WAYLAND_DISPLAY, (char_u*)display, -1);
#endif
}

#endif // FEAT_WAYLAND
