This is an automated email from the git hooks/post-receive script.
git pushed a commit to reference refs/pull/135/head
in repository enlightenment.
View the commit online.
commit eb0c253f550410f03331f47d1623e70c2fff2288
Author: [email protected] <[email protected]>
AuthorDate: Sat Jun 13 11:32:11 2026 -0600
feat(e_wl_proxy): object-id remapping and message translation
After the compositor restarts it assigns different object ids than the ones
the client holds, so the proxy keeps a bidirectional client<->backend id map
with lowest-free allocators (e_wl_proxy_idmap) and rewrites every forwarded
message in place to translate the header object id, object-typed arguments and
new_id allocations (e_wl_proxy_xlate), mutating the map as objects are created
and destroyed.
The allocator honors libwayland's dense-id rule (a client-allocated new_id
must be the next free slot) and never reuses an id until the server confirms
destruction via wl_display.delete_id, mirroring libwayland-client exactly so
the restarted compositor accepts the replayed and ongoing traffic.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
src/bin/e_wl_proxy/e_wl_proxy_idmap.c | 232 ++++++++++++++++++++++++++++++++++
src/bin/e_wl_proxy/e_wl_proxy_idmap.h | 55 ++++++++
src/bin/e_wl_proxy/e_wl_proxy_xlate.c | 147 +++++++++++++++++++++
src/bin/e_wl_proxy/e_wl_proxy_xlate.h | 28 ++++
4 files changed, 462 insertions(+)
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_idmap.c b/src/bin/e_wl_proxy/e_wl_proxy_idmap.c
new file mode 100644
index 000000000..f16784a9a
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_idmap.c
@@ -0,0 +1,232 @@
+#include "config.h"
+#include "e_wl_proxy_idmap.h"
+#include "e_wl_proxy_objmap.h"
+#include "e_wl_proxy_protocol.h"
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct _Ent
+{
+ uint32_t client_id;
+ uint32_t backend_id;
+ const struct wl_interface *iface;
+} Ent;
+
+/* lowest-free allocator: a `next` counter plus a free list of returned ids. */
+typedef struct _Alloc
+{
+ uint32_t next; /* next never-used id */
+ uint32_t *free; /* returned ids, unsorted */
+ int nfree;
+ int cap;
+} Alloc;
+
+struct _E_Wl_Proxy_Idmap
+{
+ E_Wl_Proxy_Objmap *by_client; /* client_id -> Ent* */
+ E_Wl_Proxy_Objmap *by_backend; /* backend_id -> Ent* */
+ unsigned int count;
+ Alloc client_range; /* backend ids in [2 .. server_start) */
+ Alloc server_range; /* client-facing ids in [server_start ..) */
+};
+
+static uint32_t
+_alloc_get(Alloc *a)
+{
+ if (a->nfree > 0)
+ {
+ int i, lo = 0;
+ for (i = 1; i < a->nfree; i++) if (a->free[i] < a->free[lo]) lo = i;
+ uint32_t id = a->free[lo];
+ a->free[lo] = a->free[a->nfree - 1]; /* swap-remove */
+ a->nfree--;
+ return id;
+ }
+ return a->next++;
+}
+
+static void
+_alloc_put(Alloc *a, uint32_t id)
+{
+ if (id + 1 == a->next) { a->next--; return; } /* trim the tail cheaply */
+ if (a->nfree == a->cap)
+ {
+ int ncap = a->cap ? a->cap * 2 : 16;
+ uint32_t *nf = realloc(a->free, ncap * sizeof(uint32_t));
+ if (!nf) return; /* leak the id (it just won't be reused); harmless */
+ a->free = nf; a->cap = ncap;
+ }
+ a->free[a->nfree++] = id;
+}
+
+static Ent *
+_ent_new(uint32_t cid, uint32_t bid, const struct wl_interface *iface)
+{
+ Ent *e = malloc(sizeof(*e));
+ if (!e) return NULL;
+ e->client_id = cid; e->backend_id = bid; e->iface = iface;
+ return e;
+}
+
+E_Wl_Proxy_Idmap *
+e_wl_proxy_idmap_new(void)
+{
+ E_Wl_Proxy_Idmap *m = calloc(1, sizeof(*m));
+ Ent *disp;
+ if (!m) return NULL;
+ m->by_client = e_wl_proxy_objmap_new();
+ m->by_backend = e_wl_proxy_objmap_new();
+ if (!m->by_client || !m->by_backend)
+ {
+ e_wl_proxy_objmap_free(m->by_client, NULL);
+ e_wl_proxy_objmap_free(m->by_backend, NULL);
+ free(m);
+ return NULL;
+ }
+ m->client_range.next = 2;
+ m->server_range.next = E_WL_PROXY_SERVER_ID_START;
+ disp = _ent_new(1, 1, e_wl_proxy_iface_display());
+ if (!disp) { e_wl_proxy_idmap_free(m); return NULL; }
+ e_wl_proxy_objmap_set(m->by_client, 1, disp);
+ e_wl_proxy_objmap_set(m->by_backend, 1, disp);
+ m->count = 1;
+ return m;
+}
+
+void
+e_wl_proxy_idmap_free(E_Wl_Proxy_Idmap *m)
+{
+ if (!m) return;
+ /* free the Ent objects once, via the client index; clear the backend index
+ * without a second free to avoid a double free of the shared Ents. */
+ e_wl_proxy_objmap_free(m->by_client, free);
+ e_wl_proxy_objmap_free(m->by_backend, NULL);
+ free(m->client_range.free);
+ free(m->server_range.free);
+ free(m);
+}
+
+unsigned int
+e_wl_proxy_idmap_count(E_Wl_Proxy_Idmap *m) { return m ? m->count : 0; }
+
+uint32_t
+e_wl_proxy_idmap_alloc_backend(E_Wl_Proxy_Idmap *m)
+{
+ return m ? _alloc_get(&m->client_range) : 0;
+}
+
+void
+e_wl_proxy_idmap_free_backend(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+ if (m && backend_id >= 2 && backend_id < E_WL_PROXY_SERVER_ID_START)
+ _alloc_put(&m->client_range, backend_id);
+}
+
+static void
+_del_ent(E_Wl_Proxy_Idmap *m, Ent *e)
+{
+ if (!e) return;
+ e_wl_proxy_objmap_del(m->by_client, e->client_id);
+ e_wl_proxy_objmap_del(m->by_backend, e->backend_id);
+ if (e->backend_id >= E_WL_PROXY_SERVER_ID_START)
+ _alloc_put(&m->server_range, e->client_id); /* server object: free client id */
+ else if (e->backend_id >= 2)
+ _alloc_put(&m->client_range, e->backend_id); /* client object: free backend id */
+ if (m->count) m->count--;
+ free(e);
+}
+
+/* Remove a mapping WITHOUT returning its allocated id to the free list. Used on
+ * id reuse: if the client reuses an id whose old object the backend still holds
+ * live (a tree-retained object recreated during replay, whose client-side
+ * destroy we already processed pre-crash), recycling that backend id would make
+ * the new object collide with the live old one in the backend ("invalid
+ * arguments"). Leaking the id keeps our free set a subset of the backend's. The
+ * leak is bounded (one per initially-retained-dead id) and reset each recovery. */
+static void
+_unmap_ent(E_Wl_Proxy_Idmap *m, Ent *e)
+{
+ if (!e) return;
+ e_wl_proxy_objmap_del(m->by_client, e->client_id);
+ e_wl_proxy_objmap_del(m->by_backend, e->backend_id);
+ if (m->count) m->count--;
+ free(e);
+}
+
+uint32_t
+e_wl_proxy_idmap_add_client(E_Wl_Proxy_Idmap *m, uint32_t client_id,
+ const struct wl_interface *iface)
+{
+ Ent *old, *e;
+ uint32_t bid;
+ if (!m) return 0;
+ old = e_wl_proxy_objmap_get(m->by_client, client_id);
+ if (old) _unmap_ent(m, old); /* reuse: leak old id, backend may still hold it */
+ bid = _alloc_get(&m->client_range);
+ e = _ent_new(client_id, bid, iface);
+ if (!e) { _alloc_put(&m->client_range, bid); return 0; }
+ e_wl_proxy_objmap_set(m->by_client, client_id, e);
+ e_wl_proxy_objmap_set(m->by_backend, bid, e);
+ m->count++;
+ return bid;
+}
+
+uint32_t
+e_wl_proxy_idmap_add_server(E_Wl_Proxy_Idmap *m, uint32_t backend_id,
+ const struct wl_interface *iface)
+{
+ Ent *old, *e;
+ uint32_t cid;
+ if (!m) return 0;
+ old = e_wl_proxy_objmap_get(m->by_backend, backend_id);
+ if (old) _unmap_ent(m, old); /* reuse: leak old client id */
+ cid = _alloc_get(&m->server_range);
+ e = _ent_new(cid, backend_id, iface);
+ if (!e) { _alloc_put(&m->server_range, cid); return 0; }
+ e_wl_proxy_objmap_set(m->by_client, cid, e);
+ e_wl_proxy_objmap_set(m->by_backend, backend_id, e);
+ m->count++;
+ return cid;
+}
+
+uint32_t
+e_wl_proxy_idmap_c2b(E_Wl_Proxy_Idmap *m, uint32_t client_id)
+{
+ Ent *e = m ? e_wl_proxy_objmap_get(m->by_client, client_id) : NULL;
+ return e ? e->backend_id : 0;
+}
+
+uint32_t
+e_wl_proxy_idmap_b2c(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+ Ent *e = m ? e_wl_proxy_objmap_get(m->by_backend, backend_id) : NULL;
+ return e ? e->client_id : 0;
+}
+
+const struct wl_interface *
+e_wl_proxy_idmap_iface_c(E_Wl_Proxy_Idmap *m, uint32_t client_id)
+{
+ Ent *e = m ? e_wl_proxy_objmap_get(m->by_client, client_id) : NULL;
+ return e ? e->iface : NULL;
+}
+
+const struct wl_interface *
+e_wl_proxy_idmap_iface_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+ Ent *e = m ? e_wl_proxy_objmap_get(m->by_backend, backend_id) : NULL;
+ return e ? e->iface : NULL;
+}
+
+void
+e_wl_proxy_idmap_del_c(E_Wl_Proxy_Idmap *m, uint32_t client_id)
+{
+ if (!m || client_id == 1) return; /* never unmap wl_display */
+ _del_ent(m, e_wl_proxy_objmap_get(m->by_client, client_id));
+}
+
+void
+e_wl_proxy_idmap_del_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+ if (!m || backend_id == 1) return;
+ _del_ent(m, e_wl_proxy_objmap_get(m->by_backend, backend_id));
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_idmap.h b/src/bin/e_wl_proxy/e_wl_proxy_idmap.h
new file mode 100644
index 000000000..484d6ba40
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_idmap.h
@@ -0,0 +1,55 @@
+#ifndef E_WL_PROXY_IDMAP_H
+#define E_WL_PROXY_IDMAP_H
+
+#include <stdint.h>
+#include <wayland-util.h> /* struct wl_interface */
+
+/* Server-allocated object ids start here (libwayland WL_SERVER_ID_START). */
+#define E_WL_PROXY_SERVER_ID_START 0xff000000u
+
+/* A per-connection bidirectional map between client-space ids (what the app
+ * uses; stable for the connection) and backend-space ids (what the current E
+ * connection sees; rebuilt every recovery). Lowest-free allocation in each
+ * space mirrors libwayland so the backend id stream stays dense and gapless. */
+typedef struct _E_Wl_Proxy_Idmap E_Wl_Proxy_Idmap;
+
+/* New map, pre-seeded with wl_display (client 1 <-> backend 1). NULL on OOM. */
+E_Wl_Proxy_Idmap *e_wl_proxy_idmap_new(void);
+void e_wl_proxy_idmap_free(E_Wl_Proxy_Idmap *m);
+
+/* test/query: number of live mappings (incl. the seeded wl_display). */
+unsigned int e_wl_proxy_idmap_count(E_Wl_Proxy_Idmap *m);
+
+/* Low-level allocators (lowest freed id first). alloc_backend hands out a
+ * client-range backend id; free_backend returns one to the pool. Used for the
+ * replay sync-barrier callback, which needs a real gapless backend id but no
+ * client-space mapping. Returns 0 on OOM. */
+uint32_t e_wl_proxy_idmap_alloc_backend(E_Wl_Proxy_Idmap *m);
+void e_wl_proxy_idmap_free_backend(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+/* Client created object `client_id` of `iface`: allocate a fresh lowest-free
+ * backend id (client range), record the mapping, return the backend id (0 on
+ * OOM). If `client_id` is already mapped, the old mapping is dropped first. */
+uint32_t e_wl_proxy_idmap_add_client(E_Wl_Proxy_Idmap *m, uint32_t client_id,
+ const struct wl_interface *iface);
+
+/* Server created object `backend_id` (server range) of `iface`: allocate a
+ * fresh lowest-free client-facing id (server range), record, return it (0 on
+ * OOM). If `backend_id` is already mapped, the old mapping is dropped first. */
+uint32_t e_wl_proxy_idmap_add_server(E_Wl_Proxy_Idmap *m, uint32_t backend_id,
+ const struct wl_interface *iface);
+
+/* Translate. Return 0 if the id is not mapped (caller treats as error/drop). */
+uint32_t e_wl_proxy_idmap_c2b(E_Wl_Proxy_Idmap *m, uint32_t client_id);
+uint32_t e_wl_proxy_idmap_b2c(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+/* Interface of a mapped object (for signature walking). NULL if not mapped. */
+const struct wl_interface *e_wl_proxy_idmap_iface_c(E_Wl_Proxy_Idmap *m, uint32_t client_id);
+const struct wl_interface *e_wl_proxy_idmap_iface_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+/* Destroy a mapping by either id; the freed backend/client id returns to its
+ * allocator pool (so it is reused, mirroring libwayland). No-op if absent. */
+void e_wl_proxy_idmap_del_c(E_Wl_Proxy_Idmap *m, uint32_t client_id);
+void e_wl_proxy_idmap_del_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+#endif
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_xlate.c b/src/bin/e_wl_proxy/e_wl_proxy_xlate.c
new file mode 100644
index 000000000..5ba337617
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_xlate.c
@@ -0,0 +1,147 @@
+#include "config.h"
+#include "e_wl_proxy_xlate.h"
+#include "e_wl_proxy_wire.h"
+#include "e_wl_proxy_protocol.h"
+#include <string.h>
+
+static inline size_t _pad4(size_t n) { return (n + 3) & ~((size_t)3); }
+
+int
+e_wl_proxy_xlate(unsigned char *msg, size_t len, E_Wl_Proxy_Idmap *map,
+ Eina_Bool is_request)
+{
+ E_Wl_Proxy_Hdr hdr;
+ const struct wl_interface *sender_iface;
+ const struct wl_message *m;
+ unsigned char *body;
+ const unsigned char *last_str = NULL;
+ uint32_t last_str_len = 0;
+ size_t off, body_len;
+ uint32_t sender_b; /* translated sender id */
+ const char *sig, *c;
+ int arg = 0;
+
+ if (e_wl_proxy_wire_hdr(msg, len, &hdr) != 1) return -1;
+ if (hdr.size < 8 || hdr.size > len) return -1;
+
+ /* resolve sender interface + translate the header object_id */
+ sender_iface = is_request ? e_wl_proxy_idmap_iface_c(map, hdr.object_id)
+ : e_wl_proxy_idmap_iface_b(map, hdr.object_id);
+ if (!sender_iface) return -1;
+ sender_b = is_request ? e_wl_proxy_idmap_c2b(map, hdr.object_id)
+ : e_wl_proxy_idmap_b2c(map, hdr.object_id);
+ if (!sender_b) return -1;
+ memcpy(msg, &sender_b, 4);
+
+ if (is_request)
+ {
+ if (hdr.opcode >= (uint16_t)sender_iface->method_count) return -1;
+ m = &sender_iface->methods[hdr.opcode];
+ }
+ else
+ {
+ if (hdr.opcode >= (uint16_t)sender_iface->event_count) return 0; /* unknown event: pass */
+ m = &sender_iface->events[hdr.opcode];
+ }
+
+ body = msg + 8;
+ body_len = (size_t)hdr.size - 8;
+ off = 0;
+
+ /* wl_display.delete_id(uint id): the id rides a 'u', not an 'o'. Special-case
+ * the backend->client direction: translate b->c, then unmap. */
+ if (!is_request && hdr.object_id == 1 && !strcmp(m->name, "delete_id"))
+ {
+ uint32_t freed_b, freed_c;
+ if (body_len < 4) return -1;
+ memcpy(&freed_b, body, 4);
+ freed_c = e_wl_proxy_idmap_b2c(map, freed_b);
+ if (freed_c) { memcpy(body, &freed_c, 4); e_wl_proxy_idmap_del_b(map, freed_b); }
+ return 0;
+ }
+
+ for (sig = m->signature, c = sig; *c; c++)
+ {
+ if (*c >= '0' && *c <= '9') continue;
+ if (*c == '?') continue;
+
+ switch (*c)
+ {
+ case 'i': case 'u': case 'f':
+ if (off + 4 > body_len) return -1;
+ off += 4;
+ break;
+ case 'o':
+ {
+ uint32_t oid, t;
+ if (off + 4 > body_len) return -1;
+ memcpy(&oid, body + off, 4);
+ if (oid)
+ {
+ t = is_request ? e_wl_proxy_idmap_c2b(map, oid)
+ : e_wl_proxy_idmap_b2c(map, oid);
+ if (!t) return -1; /* unmapped ref: drop msg */
+ memcpy(body + off, &t, 4);
+ }
+ off += 4;
+ }
+ break;
+ case 'n':
+ {
+ uint32_t nid, t;
+ const struct wl_interface *iface = m->types[arg];
+ if (off + 4 > body_len) return -1;
+ memcpy(&nid, body + off, 4);
+ if (!iface)
+ {
+ /* generic new_id (bind): interface is the preceding 's' */
+ if (last_str && last_str_len > 0)
+ {
+ char name[64];
+ size_t cp = last_str_len - 1; /* drop NUL */
+ if (cp >= sizeof(name)) cp = sizeof(name) - 1;
+ memcpy(name, last_str, cp); name[cp] = '\0';
+ iface = e_wl_proxy_iface_by_name(name);
+ }
+ }
+ if (is_request)
+ t = e_wl_proxy_idmap_add_client(map, nid, iface);
+ else
+ t = iface ? e_wl_proxy_idmap_add_server(map, nid, iface) : 0;
+ if (!t) return -1;
+ memcpy(body + off, &t, 4);
+ off += 4;
+ arg++;
+ continue;
+ }
+ case 's': case 'a':
+ {
+ uint32_t alen;
+ if (off + 4 > body_len) return -1;
+ memcpy(&alen, body + off, 4); off += 4;
+ if (alen > body_len - off) return -1;
+ if (off + _pad4(alen) > body_len) return -1;
+ if (*c == 's') { last_str = body + off; last_str_len = alen; }
+ off += _pad4(alen);
+ }
+ break;
+ case 'h':
+ break; /* fd: no body bytes */
+ default:
+ return -1;
+ }
+ arg++;
+ }
+
+ /* NOTE: a destructor request does NOT free the backend id here. Wayland
+ * forbids reusing an object id until the server confirms destruction via
+ * wl_display.delete_id; the backend object is still live until E processes
+ * the destroy. If we freed the id now, a later client object could reuse it
+ * before E's delete_id arrived, and that delete_id (for the old object) would
+ * then clobber the new mapping — dropping the new object's events (observed:
+ * a region destroyed, its id reused for a frame callback, the callback's
+ * "done" then untranslatable). The mapping is freed when delete_id arrives
+ * (handled above, backend->client), exactly mirroring libwayland-client. */
+
+ return 0;
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_xlate.h b/src/bin/e_wl_proxy/e_wl_proxy_xlate.h
new file mode 100644
index 000000000..8c997628a
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_xlate.h
@@ -0,0 +1,28 @@
+#ifndef E_WL_PROXY_XLATE_H
+#define E_WL_PROXY_XLATE_H
+
+#include <stddef.h>
+#include <Eina.h>
+#include "e_wl_proxy_idmap.h"
+
+/* Translate one complete wire message (8-byte header + body) in place, rewriting
+ * every id-bearing field between client and backend space via `map`, and
+ * applying the implied map mutation:
+ * - a new_id arg adds a mapping (client->backend for a request, server->client
+ * for an event) and the field is rewritten to the freshly allocated id;
+ * - a `destroy` request or wl_display.delete_id event removes the mapping and
+ * returns the id to its allocator pool.
+ *
+ * `is_request` selects direction: EINA_TRUE = client->backend (rewrite c->b,
+ * walk methods[opcode]); EINA_FALSE = backend->client (rewrite b->c, walk
+ * events[opcode]). The message length never changes (ids are fixed 4-byte
+ * fields), so `msg` is rewritten in its existing `len` bytes.
+ *
+ * Returns 0 on success. Returns -1 if the sender or a referenced object is not
+ * mapped, the opcode is out of range, or the body is malformed: the caller
+ * should DROP this message (during replay: skip the setter; during live
+ * forwarding: log and drop, the object is already gone). */
+int e_wl_proxy_xlate(unsigned char *msg, size_t len,
+ E_Wl_Proxy_Idmap *map, Eina_Bool is_request);
+
+#endif
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.