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.

Reply via email to