This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch wl/browser-all
in repository enlightenment.
View the commit online.
commit 9cf77d334f3004a48f4ebf248b2d6c351f02a674
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 16 13:27:51 2026 -0600
e_comp_wl - implement zwp_primary_selection_device_manager_v1
Middle click paste has never worked for a Wayland client under E: the
protocol that carries it was not advertised, so every toolkit fell back
to nothing at all.
The primary selection is a stripped down wl_data_device - no drag and
drop, so no actions, no accept, no finish - and what remains is close
enough to the clipboard half of the data device that E_Comp_Wl_Data_Source
and E_Comp_Wl_Data_Offer are reused unchanged. Only the two entry points
that write to the wire differ, which is what the source's function
pointers are there for; the dnd fields stay zero and nothing on this path
reads them.
It gets its own storage rather than a second slot in selection, because
the two buffers are independent: owning the clipboard says nothing about
who owns the primary selection. There is deliberately no saving of the
contents behind the client's back either - X11's PRIMARY dies with its
owner, and so does this one.
The serial naming the input event a client selected with is accepted and
ignored. Telling a stale serial from a current one would mean threading
serials through every input path, and the cost of honouring a late
set_selection is a paste buffer one selection out of date.
gtk_primary_selection, the pre-standard protocol this one replaced, is
not implemented. GTK dropped it in 2020, its XML is not in
wayland-protocols, and vendoring a dead protocol to serve clients that no
longer exist is not worth the maintenance.
wlcs PrimarySelection: 5 skipped -> 5 passed. The 5 GtkPrimarySelection
tests stay skipped, by the above.
---
src/bin/e_comp_wl.h | 13 ++
src/bin/e_comp_wl_data.c | 343 +++++++++++++++++++++++++++++++++++++
src/bin/generated/meson.build | 1 +
src/tests/wayland/globals.expected | 1 +
4 files changed, 358 insertions(+)
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index a62967b50..833c6f752 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -333,6 +333,19 @@ struct _E_Comp_Wl_Data
Ecore_Window xwl_owner;
} clipboard;
+ /* zwp_primary_selection_device_manager_v1. Deliberately its own storage
+ * rather than a second slot in selection above: the primary selection and
+ * the clipboard are independent buffers, and a client owning one says
+ * nothing about who owns the other. E_Comp_Wl_Data_Source is spelled void*
+ * here because e_comp_wl_data.h is the one including us, not the reverse. */
+ struct
+ {
+ struct wl_global *global;
+ Eina_Hash *device_resources;
+ void *source;
+ struct wl_listener source_destroy_listener;
+ } primary_selection;
+
struct
{
struct wl_resource *resource;
diff --git a/src/bin/e_comp_wl_data.c b/src/bin/e_comp_wl_data.c
index bb7b9ad06..cb3c8ac74 100644
--- a/src/bin/e_comp_wl_data.c
+++ b/src/bin/e_comp_wl_data.c
@@ -26,6 +26,7 @@
#define EXECUTIVE_MODE_ENABLED
#define E_COMP_WL
#include "e.h"
+#include "primary-selection-unstable-v1-server-protocol.h"
#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunused-parameter"
@@ -1100,6 +1101,334 @@ e_comp_wl_data_device_send_offer(E_Client *ec)
return offer_res;
}
+/* zwp_primary_selection_device_manager_v1
+ *
+ * The primary selection is the middle click paste buffer. Its protocol is a
+ * stripped down wl_data_device: no drag and drop, so no actions, no accept and
+ * no finish. What is left is close enough to the clipboard half of the data
+ * device that E_Comp_Wl_Data_Source and E_Comp_Wl_Data_Offer are reused as is
+ * - only the two entry points that write to the wire differ, which is exactly
+ * what the source's function pointers are there for. The dnd fields of both
+ * structs stay zero and nothing on this path reads them.
+ *
+ * Unlike the clipboard there is no saving of the contents behind the client's
+ * back: X11's PRIMARY dies with its owner and so does this one. */
+
+static struct wl_resource *
+_e_comp_wl_primary_selection_device_find(struct wl_client *client)
+{
+ if (!e_comp_wl->primary_selection.device_resources) return NULL;
+ return eina_hash_find(e_comp_wl->primary_selection.device_resources, &client);
+}
+
+static void
+_e_comp_wl_primary_selection_offer_cb_receive(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, const char *mime_type, int32_t fd)
+{
+ E_Comp_Wl_Data_Offer *offer;
+
+ if (!(offer = wl_resource_get_user_data(resource)))
+ {
+ close(fd);
+ return;
+ }
+
+ if (offer->source)
+ offer->source->send(offer->source, mime_type, fd);
+ else
+ close(fd);
+}
+
+static void
+_e_comp_wl_primary_selection_offer_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static const struct zwp_primary_selection_offer_v1_interface _e_primary_selection_offer_interface =
+{
+ _e_comp_wl_primary_selection_offer_cb_receive,
+ _e_comp_wl_primary_selection_offer_cb_destroy,
+};
+
+/* called by wl_resource_destroy */
+static void
+_e_comp_wl_primary_selection_offer_cb_resource_destroy(struct wl_resource *resource)
+{
+ E_Comp_Wl_Data_Offer *offer;
+
+ if (!(offer = wl_resource_get_user_data(resource))) return;
+
+ if (offer->source)
+ {
+ wl_list_remove(&offer->source_destroy_listener.link);
+ if (offer->source->offer == offer) offer->source->offer = NULL;
+ }
+
+ free(offer);
+}
+
+/* called by emission of source->destroy_signal */
+static void
+_e_comp_wl_primary_selection_offer_cb_source_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
+{
+ E_Comp_Wl_Data_Offer *offer;
+
+ if (!listener) return;
+ offer = container_of(listener, E_Comp_Wl_Data_Offer, source_destroy_listener);
+ offer->source = NULL;
+}
+
+static void
+_e_comp_wl_primary_selection_source_send_send(E_Comp_Wl_Data_Source *source, const char *mime_type, int32_t fd)
+{
+ zwp_primary_selection_source_v1_send_send(source->resource, mime_type, fd);
+ close(fd);
+}
+
+static void
+_e_comp_wl_primary_selection_source_cancelled_send(E_Comp_Wl_Data_Source *source)
+{
+ zwp_primary_selection_source_v1_send_cancelled(source->resource);
+}
+
+/* offer and destroy are byte for byte the wl_data_source requests, so the data
+ * device's handlers serve both interfaces. There is no target() on this path:
+ * that exists for wl_data_offer.accept, which the primary selection does not
+ * have, so the slot is left NULL and never dereferenced. */
+static const struct zwp_primary_selection_source_v1_interface _e_primary_selection_source_interface =
+{
+ _e_comp_wl_data_source_cb_offer,
+ _e_comp_wl_data_source_cb_destroy,
+};
+
+static struct wl_resource *
+_e_comp_wl_primary_selection_offer_create(E_Comp_Wl_Data_Source *source, struct wl_resource *device)
+{
+ E_Comp_Wl_Data_Offer *offer;
+ Eina_Iterator *it;
+ char *t;
+
+ offer = E_NEW(E_Comp_Wl_Data_Offer, 1);
+ if (!offer) return NULL;
+
+ offer->resource =
+ wl_resource_create(wl_resource_get_client(device),
+ &zwp_primary_selection_offer_v1_interface,
+ wl_resource_get_version(device), 0);
+ if (!offer->resource)
+ {
+ free(offer);
+ return NULL;
+ }
+
+ wl_resource_set_implementation(offer->resource,
+ &_e_primary_selection_offer_interface, offer,
+ _e_comp_wl_primary_selection_offer_cb_resource_destroy);
+ offer->source = source;
+ source->offer = offer;
+ offer->source_destroy_listener.notify =
+ _e_comp_wl_primary_selection_offer_cb_source_destroy;
+ wl_signal_add(&source->destroy_signal, &offer->source_destroy_listener);
+
+ zwp_primary_selection_device_v1_send_data_offer(device, offer->resource);
+
+ it = eina_array_iterator_new(source->mime_types);
+ EINA_ITERATOR_FOREACH(it, t)
+ zwp_primary_selection_offer_v1_send_offer(offer->resource, t);
+ eina_iterator_free(it);
+
+ return offer->resource;
+}
+
+/* Announce whatever the primary selection currently is to the client holding
+ * keyboard focus, or announce that there is none. */
+static void
+_e_comp_wl_primary_selection_send(void)
+{
+ struct wl_resource *device, *offer_res = NULL, *focus;
+ E_Comp_Wl_Data_Source *source;
+
+ if (!e_comp_wl->kbd.enabled) return;
+ if (!(focus = e_comp_wl->kbd.focus)) return;
+
+ device = _e_comp_wl_primary_selection_device_find(wl_resource_get_client(focus));
+ if (!device) return;
+
+ source = e_comp_wl->primary_selection.source;
+ if (source)
+ offer_res = _e_comp_wl_primary_selection_offer_create(source, device);
+
+ zwp_primary_selection_device_v1_send_selection(device, offer_res);
+}
+
+/* called by emission of the owning source's destroy_signal */
+static void
+_e_comp_wl_primary_selection_cb_source_destroy(struct wl_listener *listener EINA_UNUSED, void *data)
+{
+ if (e_comp_wl->primary_selection.source != data) return;
+
+ /* wl_signal_emit walks its listeners safely, so unhooking from inside one
+ * is fine - and it keeps the invariant that the listener is on a list
+ * exactly when there is a source to be listening to. */
+ wl_list_remove(&e_comp_wl->primary_selection.source_destroy_listener.link);
+ e_comp_wl->primary_selection.source = NULL;
+ _e_comp_wl_primary_selection_send();
+}
+
+static void
+_e_comp_wl_primary_selection_set(E_Comp_Wl_Data_Source *source)
+{
+ E_Comp_Wl_Data_Source *prev = e_comp_wl->primary_selection.source;
+
+ if (prev == source) return;
+
+ if (prev)
+ {
+ wl_list_remove(&e_comp_wl->primary_selection.source_destroy_listener.link);
+ if (prev->cancelled) prev->cancelled(prev);
+ }
+
+ e_comp_wl->primary_selection.source = source;
+
+ if (source)
+ {
+ e_comp_wl->primary_selection.source_destroy_listener.notify =
+ _e_comp_wl_primary_selection_cb_source_destroy;
+ wl_signal_add(&source->destroy_signal,
+ &e_comp_wl->primary_selection.source_destroy_listener);
+ }
+
+ _e_comp_wl_primary_selection_send();
+}
+
+/* The serial names the input event the user selected with. E has no way to
+ * tell a stale one from a current one without threading serials through every
+ * input path, and the cost of honouring a late set_selection is a paste buffer
+ * one selection out of date - so it is accepted and ignored, as it is by every
+ * other compositor that ships this. */
+static void
+_e_comp_wl_primary_selection_device_cb_selection_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, struct wl_resource *source_resource, uint32_t serial EINA_UNUSED)
+{
+ E_Comp_Wl_Data_Source *source = NULL;
+
+ if (source_resource)
+ {
+ source = wl_resource_get_user_data(source_resource);
+ if (!source) return;
+ }
+
+ _e_comp_wl_primary_selection_set(source);
+}
+
+static void
+_e_comp_wl_primary_selection_device_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static const struct zwp_primary_selection_device_v1_interface _e_primary_selection_device_interface =
+{
+ _e_comp_wl_primary_selection_device_cb_selection_set,
+ _e_comp_wl_primary_selection_device_cb_destroy,
+};
+
+static void
+_e_comp_wl_primary_selection_device_cb_unbind(struct wl_resource *resource)
+{
+ struct wl_client *wc = wl_resource_get_client(resource);
+
+ /* Only forget the client if this is the device it is still registered
+ * under: a client is free to ask for a second device and drop the first. */
+ if (_e_comp_wl_primary_selection_device_find(wc) != resource) return;
+ eina_hash_del_by_key(e_comp_wl->primary_selection.device_resources, &wc);
+}
+
+static void
+_e_comp_wl_primary_selection_manager_cb_source_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
+{
+ E_Comp_Wl_Data_Source *source;
+
+ source = E_NEW(E_Comp_Wl_Data_Source, 1);
+ if (!source)
+ {
+ wl_resource_post_no_memory(resource);
+ return;
+ }
+
+ wl_signal_init(&source->destroy_signal);
+ source->send = _e_comp_wl_primary_selection_source_send_send;
+ source->cancelled = _e_comp_wl_primary_selection_source_cancelled_send;
+
+ source->resource =
+ wl_resource_create(client, &zwp_primary_selection_source_v1_interface,
+ wl_resource_get_version(resource), id);
+ if (!source->resource)
+ {
+ free(source);
+ wl_resource_post_no_memory(resource);
+ return;
+ }
+
+ wl_resource_set_implementation(source->resource,
+ &_e_primary_selection_source_interface, source,
+ _e_comp_wl_data_source_cb_resource_destroy);
+}
+
+static void
+_e_comp_wl_primary_selection_manager_cb_device_get(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *seat_resource EINA_UNUSED)
+{
+ struct wl_resource *res;
+
+ res = wl_resource_create(client, &zwp_primary_selection_device_v1_interface,
+ wl_resource_get_version(resource), id);
+ if (!res)
+ {
+ wl_resource_post_no_memory(resource);
+ return;
+ }
+
+ eina_hash_set(e_comp_wl->primary_selection.device_resources, &client, res);
+ wl_resource_set_implementation(res, &_e_primary_selection_device_interface,
+ NULL, _e_comp_wl_primary_selection_device_cb_unbind);
+
+ /* A device created while this client already holds focus has missed the
+ * announcement that went out when the selection was set. */
+ if ((e_comp_wl->kbd.enabled) && (e_comp_wl->kbd.focus) &&
+ (wl_resource_get_client(e_comp_wl->kbd.focus) == client))
+ _e_comp_wl_primary_selection_send();
+}
+
+static void
+_e_comp_wl_primary_selection_manager_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static const struct zwp_primary_selection_device_manager_v1_interface _e_primary_selection_manager_interface =
+{
+ _e_comp_wl_primary_selection_manager_cb_source_create,
+ _e_comp_wl_primary_selection_manager_cb_device_get,
+ _e_comp_wl_primary_selection_manager_cb_destroy,
+};
+
+static void
+_e_comp_wl_primary_selection_cb_bind_manager(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id)
+{
+ struct wl_resource *res;
+
+ res = wl_resource_create(client,
+ &zwp_primary_selection_device_manager_v1_interface,
+ version, id);
+ if (!res)
+ {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ wl_resource_set_implementation(res, &_e_primary_selection_manager_interface,
+ NULL, NULL);
+}
+
E_API void
e_comp_wl_data_device_keyboard_focus_set(void)
{
@@ -1107,6 +1436,11 @@ e_comp_wl_data_device_keyboard_focus_set(void)
E_Comp_Wl_Data_Source *source;
E_Client *focused;
+ /* The primary selection follows keyboard focus on its own terms - it has
+ * no XWayland half to negotiate, so it must not be caught by the early
+ * returns below. */
+ _e_comp_wl_primary_selection_send();
+
if (!e_comp_wl->kbd.enabled)
{
ERR("Keyboard not enabled");
@@ -1172,6 +1506,14 @@ e_comp_wl_data_manager_init(void)
_e_comp_wl_clipboard_create();
e_comp_wl->mgr.data_resources = eina_hash_pointer_new(NULL);
+ e_comp_wl->primary_selection.device_resources = eina_hash_pointer_new(NULL);
+ e_comp_wl->primary_selection.global =
+ wl_global_create(e_comp_wl->wl.disp,
+ &zwp_primary_selection_device_manager_v1_interface, 1,
+ NULL, _e_comp_wl_primary_selection_cb_bind_manager);
+ if (!e_comp_wl->primary_selection.global)
+ ERR("Could not create global for primary selection device manager");
+
return EINA_TRUE;
}
@@ -1183,6 +1525,7 @@ e_comp_wl_data_manager_shutdown(void)
wl_list_remove(&e_comp_wl->clipboard.listener.link);
E_FREE_FUNC(e_comp_wl->mgr.data_resources, eina_hash_free);
+ E_FREE_FUNC(e_comp_wl->primary_selection.device_resources, eina_hash_free);
}
E_API struct wl_resource *
diff --git a/src/bin/generated/meson.build b/src/bin/generated/meson.build
index 5b62da3ab..473c234c8 100644
--- a/src/bin/generated/meson.build
+++ b/src/bin/generated/meson.build
@@ -9,6 +9,7 @@ protos = [
'@0@/staging/xdg-activation/xdg-activation-v1.xml'.format(dir_wayland_protocols),
'@0@/stable/viewporter/viewporter.xml'.format(dir_wayland_protocols),
'@0@/unstable/xdg-output/xdg-output-unstable-v1.xml'.format(dir_wayland_protocols),
+ '@0@/unstable/primary-selection/primary-selection-unstable-v1.xml'.format(dir_wayland_protocols),
]
proto_c = []
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
index 6197f36ab..79ebd41a0 100644
--- a/src/tests/wayland/globals.expected
+++ b/src/tests/wayland/globals.expected
@@ -12,6 +12,7 @@ xdg_activation_v1 1
xdg_wm_base 6
zwp_e_session_recovery 1
zwp_pointer_constraints_v1 1
+zwp_primary_selection_device_manager_v1 1
zwp_relative_pointer_manager_v1 1
zxdg_exporter_v1 1
zxdg_importer_v1 1
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.