summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSunderland93 <[email protected]>2026-08-01 12:46:43 +0400
committerLuke from DC <[email protected]>2026-08-19 19:15:44 +0000
commit6bc2c99d15d9b2cd8e38ee834e10632ad60af101 (patch)
tree1181a325053c7817b70981eb55ab95e40171a136
parent537e98c4ca8dc78f25d796af12f7f3a25b070e9b (diff)
downloadmate-settings-daemon-6bc2c99d15d9b2cd8e38ee834e10632ad60af101.tar.bz2
mate-settings-daemon-6bc2c99d15d9b2cd8e38ee834e10632ad60af101.tar.xz
clipboard-manager: add Wayland support
-rw-r--r--configure.ac19
-rw-r--r--plugins/clipboard/Makefile.am35
-rw-r--r--plugins/clipboard/clipboard.mate-settings-plugin.desktop.in1
-rw-r--r--plugins/clipboard/msd-clipboard-manager-wayland.c703
-rw-r--r--plugins/clipboard/msd-clipboard-manager-wayland.h39
-rw-r--r--plugins/clipboard/msd-clipboard-manager.c88
6 files changed, 859 insertions, 26 deletions
diff --git a/configure.ac b/configure.ac
index e03fe82..6ea0ba5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -104,11 +104,27 @@ if test "x${enable_wayland}" != "xno"; then
CPPFLAGS="$save_CPPFLAGS"
if test "x${have_wayland}" = "xyes"; then
+ PKG_CHECK_MODULES(WAYLAND_CLIENT, wayland-client >= 1.18,
+ [have_wayland=yes],
+ [have_wayland=no])
+ fi
+
+ if test "x${have_wayland}" = "xyes"; then
+ WAYLAND_SCANNER=wayland-scanner
+ AC_SUBST(WAYLAND_SCANNER)
+ WAYLAND_PROTOCOLS_DATADIR=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`
+ AC_SUBST(WAYLAND_PROTOCOLS_DATADIR)
+ if test "x${WAYLAND_PROTOCOLS_DATADIR}" = "x"; then
+ have_wayland=no
+ fi
+ fi
+
+ if test "x${have_wayland}" = "xyes"; then
AC_DEFINE(HAVE_WAYLAND, 1, [Define if Wayland support is enabled])
fi
if test "x${have_wayland}" = "xno" -a "x${enable_wayland}" = "xyes"; then
- AC_MSG_ERROR([Wayland support requested but the GTK+ library does not have the Wayland backend])
+ AC_MSG_ERROR([Wayland support requested but the required Wayland libraries (GTK+ Wayland backend, wayland-client or wayland-protocols) are missing])
fi
fi
AM_CONDITIONAL(BUILD_WAYLAND, test "x${have_wayland}" = "xyes")
@@ -530,6 +546,7 @@ Configure summary:
Smartcard support .......: ${have_smartcard_support}
RFKill support ..........: ${enable_rfkill}
Wayland support .........: ${have_wayland}
+ Wayland protocols dir ...: ${WAYLAND_PROTOCOLS_DATADIR}
${NSS_DATABASE:+\
System nssdb ............: ${NSS_DATABASE}
}\
diff --git a/plugins/clipboard/Makefile.am b/plugins/clipboard/Makefile.am
index 414fff1..19c9c24 100644
--- a/plugins/clipboard/Makefile.am
+++ b/plugins/clipboard/Makefile.am
@@ -1,5 +1,25 @@
NULL =
+if BUILD_WAYLAND
+WAYLAND_PROTOCOL_XML = $(WAYLAND_PROTOCOLS_DATADIR)/staging/ext-data-control/ext-data-control-v1.xml
+WAYLAND_PROTOCOL_GENERATED = \
+ ext-data-control-v1-client.c \
+ ext-data-control-v1-client.h \
+ $(NULL)
+WAYLAND_CLIPBOARD_SOURCES = \
+ msd-clipboard-manager-wayland.c \
+ msd-clipboard-manager-wayland.h \
+ $(NULL)
+else
+WAYLAND_PROTOCOL_XML =
+WAYLAND_PROTOCOL_GENERATED =
+WAYLAND_CLIPBOARD_SOURCES =
+endif
+
+BUILT_SOURCES = \
+ $(WAYLAND_PROTOCOL_GENERATED) \
+ $(NULL)
+
plugin_LTLIBRARIES = \
libclipboard.la \
$(NULL)
@@ -9,19 +29,23 @@ libclipboard_la_SOURCES = \
msd-clipboard-plugin.c \
msd-clipboard-manager.h \
msd-clipboard-manager.c \
+ $(WAYLAND_CLIPBOARD_SOURCES) \
xutils.h \
xutils.c \
list.h \
list.c \
+ $(WAYLAND_PROTOCOL_GENERATED) \
$(NULL)
libclipboard_la_CPPFLAGS = \
-I$(top_srcdir)/mate-settings-daemon \
-DMATE_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CPPFLAGS)
libclipboard_la_CFLAGS = \
$(SETTINGS_PLUGIN_CFLAGS) \
+ $(WAYLAND_CLIENT_CFLAGS) \
$(AM_CFLAGS) \
$(WARN_CFLAGS)
@@ -33,8 +57,17 @@ libclipboard_la_LIBADD = \
$(SETTINGS_PLUGIN_LIBS) \
$(X11_LIBS) \
$(XINPUT_LIBS) \
+ $(WAYLAND_CLIENT_LIBS) \
$(NULL)
+$(WAYLAND_PROTOCOL_GENERATED): $(WAYLAND_PROTOCOL_XML)
+
+ext-data-control-v1-client.h: $(WAYLAND_PROTOCOL_XML)
+ $(AM_V_GEN)$(WAYLAND_SCANNER) client-header $(WAYLAND_PROTOCOL_XML) $@
+
+ext-data-control-v1-client.c: $(WAYLAND_PROTOCOL_XML)
+ $(AM_V_GEN)$(WAYLAND_SCANNER) private-code $(WAYLAND_PROTOCOL_XML) $@
+
plugin_in_files = \
clipboard.mate-settings-plugin.desktop.in \
$(NULL)
@@ -47,10 +80,12 @@ EXTRA_DIST = \
CLEANFILES = \
$(plugin_DATA) \
+ $(WAYLAND_PROTOCOL_GENERATED) \
$(NULL)
DISTCLEANFILES = \
$(plugin_DATA) \
+ $(WAYLAND_PROTOCOL_GENERATED) \
$(NULL)
$(plugin_DATA): $(plugin_in_files)
diff --git a/plugins/clipboard/clipboard.mate-settings-plugin.desktop.in b/plugins/clipboard/clipboard.mate-settings-plugin.desktop.in
index 3641bb4..3b81564 100644
--- a/plugins/clipboard/clipboard.mate-settings-plugin.desktop.in
+++ b/plugins/clipboard/clipboard.mate-settings-plugin.desktop.in
@@ -1,7 +1,6 @@
[MATE Settings Plugin]
Module=clipboard
IAge=0
-X11Only=true
Name=Clipboard
Description=Clipboard plugin
Authors=Matthias Clasen
diff --git a/plugins/clipboard/msd-clipboard-manager-wayland.c b/plugins/clipboard/msd-clipboard-manager-wayland.c
new file mode 100644
index 0000000..59babe8
--- /dev/null
+++ b/plugins/clipboard/msd-clipboard-manager-wayland.c
@@ -0,0 +1,703 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <wayland-client.h>
+
+#include "ext-data-control-v1-client.h"
+#include "msd-clipboard-manager-wayland.h"
+
+typedef enum
+{
+ MSD_WAYLAND_SELECTION_CLIPBOARD,
+ MSD_WAYLAND_SELECTION_PRIMARY
+} SelectionKind;
+
+typedef struct
+{
+ gchar *mime;
+ GByteArray *data;
+} ClipEntry;
+
+typedef struct _MsdClipboardManagerWayland MsdClipboardManagerWayland;
+
+typedef struct
+{
+ MsdClipboardManagerWayland *wl;
+ SelectionKind kind;
+ gint fd;
+ guint source_id;
+ gchar *mime;
+ GByteArray *data;
+ gboolean done;
+} ClipReceive;
+
+typedef struct
+{
+ MsdClipboardManagerWayland *wl;
+ SelectionKind kind;
+ struct ext_data_control_offer_v1 *offer;
+ GPtrArray *cache;
+ GPtrArray *receives;
+ struct ext_data_control_source_v1 *source;
+ gboolean reoffer_pending;
+} SelectionState;
+
+struct _MsdClipboardManagerWayland
+{
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct ext_data_control_manager_v1 *dc_manager;
+ struct wl_seat *seat;
+ struct ext_data_control_device_v1 *device;
+ guint display_source;
+
+ struct ext_data_control_offer_v1 *pending_offer;
+ GPtrArray *pending_mimes;
+
+ SelectionState clipboard;
+ SelectionState primary;
+};
+
+static void selection_state_clear (SelectionState *state);
+
+static void
+clip_entry_free (gpointer data)
+{
+ ClipEntry *entry = data;
+
+ if (entry == NULL)
+ return;
+
+ g_free (entry->mime);
+ if (entry->data != NULL)
+ g_byte_array_free (entry->data, TRUE);
+ g_free (entry);
+}
+
+static SelectionState *
+state_from_kind (MsdClipboardManagerWayland *wl,
+ SelectionKind kind)
+{
+ if (kind == MSD_WAYLAND_SELECTION_PRIMARY)
+ return &wl->primary;
+
+ return &wl->clipboard;
+}
+
+static void
+start_reoffer (SelectionState *state);
+
+static void
+finalize_cache (SelectionState *state)
+{
+ GPtrArray *new_cache;
+ guint i;
+
+ if (state->receives == NULL)
+ return;
+
+ new_cache = g_ptr_array_new_with_free_func (clip_entry_free);
+
+ for (i = 0; i < state->receives->len; i++) {
+ ClipReceive *recv;
+ ClipEntry *entry;
+
+ recv = state->receives->pdata[i];
+
+ entry = g_new0 (ClipEntry, 1);
+ entry->mime = recv->mime;
+ entry->data = recv->data;
+ recv->mime = NULL;
+ recv->data = NULL;
+
+ g_ptr_array_add (new_cache, entry);
+ }
+
+ if (state->cache != NULL)
+ g_ptr_array_free (state->cache, TRUE);
+ state->cache = new_cache;
+
+ for (i = 0; i < state->receives->len; i++) {
+ ClipReceive *recv;
+
+ recv = state->receives->pdata[i];
+ if (recv->fd != -1)
+ close (recv->fd);
+ g_free (recv);
+ }
+ g_ptr_array_free (state->receives, FALSE);
+ state->receives = NULL;
+}
+
+static void
+receive_finish (ClipReceive *recv)
+{
+ SelectionState *state;
+ guint i;
+
+ if (recv->done)
+ return;
+ recv->done = TRUE;
+
+ if (recv->fd != -1) {
+ close (recv->fd);
+ recv->fd = -1;
+ }
+
+ state = state_from_kind (recv->wl, recv->kind);
+
+ if (state->receives == NULL)
+ return;
+
+ for (i = 0; i < state->receives->len; i++) {
+ ClipReceive *other;
+
+ other = state->receives->pdata[i];
+ if (!other->done)
+ return;
+ }
+
+ finalize_cache (state);
+
+ if (state->reoffer_pending) {
+ state->reoffer_pending = FALSE;
+ start_reoffer (state);
+ }
+}
+
+static void
+receive_cancel (ClipReceive *recv)
+{
+ if (recv->source_id != 0)
+ g_source_remove (recv->source_id);
+ if (recv->fd != -1)
+ close (recv->fd);
+ g_free (recv->mime);
+ if (recv->data != NULL)
+ g_byte_array_free (recv->data, TRUE);
+ g_free (recv);
+}
+
+static gboolean
+receive_dispatch_cb (gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ ClipReceive *recv = user_data;
+ guchar buf[4096];
+ gssize n;
+
+ n = read (fd, buf, sizeof (buf));
+ if (n > 0) {
+ g_byte_array_append (recv->data, buf, n);
+ return G_SOURCE_CONTINUE;
+ }
+ if (n == 0) {
+ receive_finish (recv);
+ return G_SOURCE_REMOVE;
+ }
+ if (n < 0 && errno == EINTR)
+ return G_SOURCE_CONTINUE;
+ if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ receive_finish (recv);
+ return G_SOURCE_REMOVE;
+ }
+ return G_SOURCE_CONTINUE;
+ }
+
+ receive_finish (recv);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+start_receives (SelectionState *state,
+ struct ext_data_control_offer_v1 *offer,
+ GPtrArray *mimes)
+{
+ guint i;
+
+ if (state->receives != NULL) {
+ for (i = 0; i < state->receives->len; i++)
+ receive_cancel (state->receives->pdata[i]);
+ g_ptr_array_free (state->receives, FALSE);
+ state->receives = NULL;
+ }
+
+ state->receives = g_ptr_array_new ();
+
+ if (mimes == NULL)
+ return;
+
+ for (i = 0; i < mimes->len; i++) {
+ ClipReceive *recv;
+ int fds[2];
+
+ if (pipe (fds) < 0)
+ continue;
+
+ ext_data_control_offer_v1_receive (offer, mimes->pdata[i], fds[1]);
+ close (fds[1]);
+
+ recv = g_new0 (ClipReceive, 1);
+ recv->wl = state->wl;
+ recv->kind = state->kind;
+ recv->fd = fds[0];
+ recv->mime = g_strdup (mimes->pdata[i]);
+ recv->data = g_byte_array_new ();
+ fcntl (recv->fd, F_SETFL, O_NONBLOCK);
+ recv->source_id = g_unix_fd_add (recv->fd,
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ receive_dispatch_cb,
+ recv);
+ g_ptr_array_add (state->receives, recv);
+ }
+}
+
+static void
+source_write_all (gint fd,
+ const guint8 *data,
+ gsize len)
+{
+ gsize total = 0;
+ gint flags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags != -1)
+ fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+
+ while (total < len) {
+ gssize n;
+
+ n = write (fd, data + total, len - total);
+ if (n > 0) {
+ total += n;
+ continue;
+ }
+ if (n < 0 && errno == EINTR)
+ continue;
+ if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ GPollFD pfd;
+
+ pfd.fd = fd;
+ pfd.events = G_IO_OUT;
+ if (g_poll (&pfd, 1, 10000) <= 0)
+ break;
+ continue;
+ }
+ break;
+ }
+
+ close (fd);
+}
+
+static void
+handle_source_send (void *data,
+ struct ext_data_control_source_v1 *source,
+ const char *mime_type,
+ int32_t fd)
+{
+ SelectionState *state = data;
+ guint i;
+
+ if (state->cache == NULL) {
+ close (fd);
+ return;
+ }
+
+ for (i = 0; i < state->cache->len; i++) {
+ ClipEntry *entry;
+
+ entry = state->cache->pdata[i];
+ if (g_strcmp0 (entry->mime, mime_type) == 0) {
+ source_write_all (fd, entry->data->data, entry->data->len);
+ return;
+ }
+ }
+
+ close (fd);
+}
+
+static void
+handle_source_cancelled (void *data,
+ struct ext_data_control_source_v1 *source)
+{
+ SelectionState *state = data;
+
+ if (state->source == source) {
+ ext_data_control_source_v1_destroy (source);
+ state->source = NULL;
+ }
+}
+
+static const struct ext_data_control_source_v1_listener source_listener = {
+ .send = handle_source_send,
+ .cancelled = handle_source_cancelled,
+};
+
+static void
+start_reoffer (SelectionState *state)
+{
+ struct ext_data_control_source_v1 *source;
+ guint i;
+
+ if (state->cache == NULL || state->cache->len == 0)
+ return;
+ if (state->wl->dc_manager == NULL || state->wl->device == NULL)
+ return;
+ if (state->source != NULL)
+ return;
+
+ source = ext_data_control_manager_v1_create_data_source (state->wl->dc_manager);
+ ext_data_control_source_v1_add_listener (source, &source_listener, state);
+
+ for (i = 0; i < state->cache->len; i++) {
+ ClipEntry *entry;
+
+ entry = state->cache->pdata[i];
+ ext_data_control_source_v1_offer (source, entry->mime);
+ }
+
+ state->source = source;
+
+ if (state->kind == MSD_WAYLAND_SELECTION_PRIMARY)
+ ext_data_control_device_v1_set_primary_selection (state->wl->device, source);
+ else
+ ext_data_control_device_v1_set_selection (state->wl->device, source);
+
+ wl_display_flush (state->wl->display);
+}
+
+static void
+handle_offer (void *data,
+ struct ext_data_control_offer_v1 *offer,
+ const char *mime_type)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ if (wl->pending_offer == offer && wl->pending_mimes != NULL)
+ g_ptr_array_add (wl->pending_mimes, g_strdup (mime_type));
+}
+
+static const struct ext_data_control_offer_v1_listener offer_listener = {
+ .offer = handle_offer,
+};
+
+static void
+handle_selection_for (MsdClipboardManagerWayland *wl,
+ SelectionState *state,
+ struct ext_data_control_offer_v1 *offer)
+{
+ if (offer != NULL && state->source != NULL) {
+ /* The compositor is echoing our own set_selection back to
+ * us: state->source is still the current selection (it was
+ * not cancelled), so this offer describes our own data.
+ * Keep the source alive and ignore the echoed offer.
+ */
+ if (state->offer != NULL && state->offer != offer)
+ ext_data_control_offer_v1_destroy (state->offer);
+ state->offer = offer;
+
+ if (wl->pending_offer != NULL)
+ wl->pending_offer = NULL;
+ if (wl->pending_mimes != NULL) {
+ g_ptr_array_free (wl->pending_mimes, TRUE);
+ wl->pending_mimes = NULL;
+ }
+ return;
+ }
+
+ if (state->source != NULL) {
+ ext_data_control_source_v1_destroy (state->source);
+ state->source = NULL;
+ }
+
+ if (state->offer != NULL && state->offer != offer) {
+ ext_data_control_offer_v1_destroy (state->offer);
+ state->offer = NULL;
+ }
+
+ if (offer == NULL) {
+ state->offer = NULL;
+
+ if (state->receives != NULL && state->receives->len > 0)
+ state->reoffer_pending = TRUE;
+ else
+ start_reoffer (state);
+
+ return;
+ }
+
+ state->offer = offer;
+ state->reoffer_pending = FALSE;
+
+ if (wl->pending_offer == offer && wl->pending_mimes != NULL) {
+ GPtrArray *mimes;
+
+ mimes = wl->pending_mimes;
+ wl->pending_mimes = NULL;
+ wl->pending_offer = NULL;
+
+ start_receives (state, offer, mimes);
+ g_ptr_array_free (mimes, TRUE);
+ } else {
+ start_receives (state, offer, NULL);
+ }
+}
+
+static void
+handle_selection (void *data,
+ struct ext_data_control_device_v1 *device,
+ struct ext_data_control_offer_v1 *offer)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ handle_selection_for (wl, &wl->clipboard, offer);
+}
+
+static void
+handle_primary_selection (void *data,
+ struct ext_data_control_device_v1 *device,
+ struct ext_data_control_offer_v1 *offer)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ handle_selection_for (wl, &wl->primary, offer);
+}
+
+static void
+handle_data_offer (void *data,
+ struct ext_data_control_device_v1 *device,
+ struct ext_data_control_offer_v1 *offer)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ if (wl->pending_offer != NULL)
+ ext_data_control_offer_v1_destroy (wl->pending_offer);
+ if (wl->pending_mimes != NULL)
+ g_ptr_array_free (wl->pending_mimes, TRUE);
+
+ wl->pending_offer = offer;
+ wl->pending_mimes = g_ptr_array_new_with_free_func (g_free);
+ ext_data_control_offer_v1_add_listener (offer, &offer_listener, wl);
+}
+
+static void
+handle_finished (void *data,
+ struct ext_data_control_device_v1 *device)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ ext_data_control_device_v1_destroy (device);
+ wl->device = NULL;
+}
+
+static const struct ext_data_control_device_v1_listener device_listener = {
+ .data_offer = handle_data_offer,
+ .selection = handle_selection,
+ .finished = handle_finished,
+ .primary_selection = handle_primary_selection,
+};
+
+static void
+registry_handle_global (void *data,
+ struct wl_registry *registry,
+ uint32_t name,
+ const char *interface,
+ uint32_t version)
+{
+ MsdClipboardManagerWayland *wl = data;
+
+ if (g_strcmp0 (interface, "ext_data_control_manager_v1") == 0) {
+ wl->dc_manager = wl_registry_bind (registry,
+ name,
+ &ext_data_control_manager_v1_interface,
+ MIN (version, 1u));
+ } else if (g_strcmp0 (interface, "wl_seat") == 0) {
+ wl->seat = wl_registry_bind (registry,
+ name,
+ &wl_seat_interface,
+ MIN (version, 5u));
+ }
+}
+
+static void
+registry_handle_global_remove (void *data,
+ struct wl_registry *registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_global,
+ .global_remove = registry_handle_global_remove,
+};
+
+static gboolean
+display_dispatch_cb (gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ MsdClipboardManagerWayland *wl = user_data;
+
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ g_warning ("Clipboard manager: Wayland connection lost");
+ return G_SOURCE_REMOVE;
+ }
+
+ if (wl_display_dispatch (wl->display) == -1) {
+ g_warning ("Clipboard manager: Failed to dispatch Wayland events");
+ return G_SOURCE_REMOVE;
+ }
+
+ wl_display_flush (wl->display);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+selection_state_clear (SelectionState *state)
+{
+ guint i;
+
+ if (state->source != NULL) {
+ ext_data_control_source_v1_destroy (state->source);
+ state->source = NULL;
+ }
+
+ if (state->offer != NULL) {
+ ext_data_control_offer_v1_destroy (state->offer);
+ state->offer = NULL;
+ }
+
+ if (state->receives != NULL) {
+ for (i = 0; i < state->receives->len; i++)
+ receive_cancel (state->receives->pdata[i]);
+ g_ptr_array_free (state->receives, FALSE);
+ state->receives = NULL;
+ }
+
+ if (state->cache != NULL) {
+ g_ptr_array_free (state->cache, TRUE);
+ state->cache = NULL;
+ }
+}
+
+MsdClipboardManagerWayland *
+msd_clipboard_manager_wayland_new (GError **error)
+{
+ MsdClipboardManagerWayland *wl;
+
+ wl = g_new0 (MsdClipboardManagerWayland, 1);
+
+ wl->display = wl_display_connect (NULL);
+ if (wl->display == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not connect to the Wayland compositor");
+ g_free (wl);
+ return NULL;
+ }
+
+ wl->clipboard.wl = wl;
+ wl->clipboard.kind = MSD_WAYLAND_SELECTION_CLIPBOARD;
+ wl->primary.wl = wl;
+ wl->primary.kind = MSD_WAYLAND_SELECTION_PRIMARY;
+
+ wl->registry = wl_display_get_registry (wl->display);
+ wl_registry_add_listener (wl->registry, &registry_listener, wl);
+
+ wl_display_roundtrip (wl->display);
+
+ if (wl->dc_manager == NULL || wl->seat == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The compositor does not support the ext-data-control protocol");
+ msd_clipboard_manager_wayland_destroy (wl);
+ return NULL;
+ }
+
+ wl->device = ext_data_control_manager_v1_get_data_device (wl->dc_manager, wl->seat);
+ ext_data_control_device_v1_add_listener (wl->device, &device_listener, wl);
+
+ wl->display_source = g_unix_fd_add (wl_display_get_fd (wl->display),
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ display_dispatch_cb,
+ wl);
+
+ wl_display_roundtrip (wl->display);
+
+ return wl;
+}
+
+void
+msd_clipboard_manager_wayland_destroy (MsdClipboardManagerWayland *wl)
+{
+ if (wl == NULL)
+ return;
+
+ if (wl->display_source != 0) {
+ g_source_remove (wl->display_source);
+ wl->display_source = 0;
+ }
+
+ selection_state_clear (&wl->clipboard);
+ selection_state_clear (&wl->primary);
+
+ if (wl->pending_offer != NULL) {
+ ext_data_control_offer_v1_destroy (wl->pending_offer);
+ wl->pending_offer = NULL;
+ }
+ if (wl->pending_mimes != NULL) {
+ g_ptr_array_free (wl->pending_mimes, TRUE);
+ wl->pending_mimes = NULL;
+ }
+
+ if (wl->device != NULL) {
+ ext_data_control_device_v1_destroy (wl->device);
+ wl->device = NULL;
+ }
+ if (wl->dc_manager != NULL) {
+ ext_data_control_manager_v1_destroy (wl->dc_manager);
+ wl->dc_manager = NULL;
+ }
+ if (wl->seat != NULL) {
+ wl_seat_destroy (wl->seat);
+ wl->seat = NULL;
+ }
+ if (wl->registry != NULL) {
+ wl_registry_destroy (wl->registry);
+ wl->registry = NULL;
+ }
+ if (wl->display != NULL) {
+ wl_display_disconnect (wl->display);
+ wl->display = NULL;
+ }
+
+ g_free (wl);
+}
diff --git a/plugins/clipboard/msd-clipboard-manager-wayland.h b/plugins/clipboard/msd-clipboard-manager-wayland.h
new file mode 100644
index 0000000..886ed4d
--- /dev/null
+++ b/plugins/clipboard/msd-clipboard-manager-wayland.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2026 MATE Developers
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __MSD_CLIPBOARD_MANAGER_WAYLAND_H
+#define __MSD_CLIPBOARD_MANAGER_WAYLAND_H
+
+#include <glib-object.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _MsdClipboardManagerWayland MsdClipboardManagerWayland;
+
+MsdClipboardManagerWayland *msd_clipboard_manager_wayland_new (GError **error);
+void msd_clipboard_manager_wayland_destroy (MsdClipboardManagerWayland *manager);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MSD_CLIPBOARD_MANAGER_WAYLAND_H */
diff --git a/plugins/clipboard/msd-clipboard-manager.c b/plugins/clipboard/msd-clipboard-manager.c
index c1b68a2..b885b89 100644
--- a/plugins/clipboard/msd-clipboard-manager.c
+++ b/plugins/clipboard/msd-clipboard-manager.c
@@ -41,6 +41,9 @@
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif /* GDK_WINDOWING_X11 */
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif /* GDK_WINDOWING_WAYLAND */
#include <X11/Xlib.h>
#include <X11/Xatom.h>
@@ -49,6 +52,9 @@
#include "mate-settings-profile.h"
#include "msd-clipboard-manager.h"
+#ifdef HAVE_WAYLAND
+#include "msd-clipboard-manager-wayland.h"
+#endif /* HAVE_WAYLAND */
struct MsdClipboardManagerPrivate
{
@@ -62,6 +68,10 @@ struct MsdClipboardManagerPrivate
Window requestor;
Atom property;
Time time;
+
+#ifdef HAVE_WAYLAND
+ MsdClipboardManagerWayland *wayland;
+#endif /* HAVE_WAYLAND */
};
typedef struct
@@ -939,24 +949,34 @@ gboolean
mate_settings_profile_start (NULL);
#ifdef GDK_WINDOWING_X11
- if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "X11 display is required for the clipboard plugin");
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ g_idle_add ((GSourceFunc) start_clipboard_idle_cb, manager);
+
mate_settings_profile_end (NULL);
- return FALSE;
+
+ return TRUE;
}
-#else
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "The clipboard plugin was built without X11 support");
- mate_settings_profile_end (NULL);
- return FALSE;
#endif /* GDK_WINDOWING_X11 */
- g_idle_add ((GSourceFunc) start_clipboard_idle_cb, manager);
+#if defined (HAVE_WAYLAND) && defined (GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ manager->priv->wayland = msd_clipboard_manager_wayland_new (error);
+ if (manager->priv->wayland == NULL) {
+ mate_settings_profile_end (NULL);
+ return FALSE;
+ }
+ mate_settings_profile_end (NULL);
+
+ return TRUE;
+ }
+#endif /* HAVE_WAYLAND && GDK_WINDOWING_WAYLAND */
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "The clipboard plugin requires an X11 or a Wayland display with the ext-data-control protocol");
mate_settings_profile_end (NULL);
- return TRUE;
+ return FALSE;
}
void
@@ -965,22 +985,31 @@ msd_clipboard_manager_stop (MsdClipboardManager *manager)
g_debug ("Stopping clipboard manager");
#ifdef GDK_WINDOWING_X11
- if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- return;
-#endif /* GDK_WINDOWING_X11 */
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ clipboard_manager_watch_cb (manager,
+ manager->priv->window,
+ FALSE,
+ 0,
+ NULL);
+ XDestroyWindow (manager->priv->display, manager->priv->window);
- clipboard_manager_watch_cb (manager,
- manager->priv->window,
- FALSE,
- 0,
- NULL);
- XDestroyWindow (manager->priv->display, manager->priv->window);
+ list_foreach (manager->priv->conversions, (Callback) conversion_free, NULL);
+ list_free (manager->priv->conversions);
+
+ list_foreach (manager->priv->contents, (Callback) target_data_unref, NULL);
+ list_free (manager->priv->contents);
- list_foreach (manager->priv->conversions, (Callback) conversion_free, NULL);
- list_free (manager->priv->conversions);
+ return;
+ }
+#endif /* GDK_WINDOWING_X11 */
- list_foreach (manager->priv->contents, (Callback) target_data_unref, NULL);
- list_free (manager->priv->contents);
+#if defined (HAVE_WAYLAND) && defined (GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
+ msd_clipboard_manager_wayland_destroy (manager->priv->wayland);
+ manager->priv->wayland = NULL;
+ return;
+ }
+#endif /* HAVE_WAYLAND && GDK_WINDOWING_WAYLAND */
}
static void
@@ -996,6 +1025,10 @@ msd_clipboard_manager_init (MsdClipboardManager *manager)
{
manager->priv = msd_clipboard_manager_get_instance_private (manager);
+#ifdef HAVE_WAYLAND
+ manager->priv->wayland = NULL;
+#endif /* HAVE_WAYLAND */
+
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
manager->priv->display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
@@ -1018,6 +1051,13 @@ msd_clipboard_manager_finalize (GObject *object)
g_return_if_fail (clipboard_manager->priv != NULL);
+#if defined (HAVE_WAYLAND) && defined (GDK_WINDOWING_WAYLAND)
+ if (clipboard_manager->priv->wayland != NULL) {
+ msd_clipboard_manager_wayland_destroy (clipboard_manager->priv->wayland);
+ clipboard_manager->priv->wayland = NULL;
+ }
+#endif /* HAVE_WAYLAND && GDK_WINDOWING_WAYLAND */
+
G_OBJECT_CLASS (msd_clipboard_manager_parent_class)->finalize (object);
}