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 069bffc34c19bcbb595b820d5edbaa20af524be7
Author: [email protected] <[email protected]>
AuthorDate: Sat Jun 13 11:32:11 2026 -0600
feat(e_wl_proxy): per-client shadow object tree
Track every object a client creates from the live request stream so the proxy
can reconstruct the session against a freshly restarted compositor. The tree
records each object's constructor and its latest state-setting requests
(last-wins per opcode, except order-significant setters such as
wl_shm_pool.resize which are kept in full), follows create/bind/destroy, and
captures wl_registry globals from the event stream.
A dependency refcount keeps objects that are still referenced (e.g. a region
named by a live surface's set_opaque_region, or a wl_shm_pool whose buffers
outlive it) alive for replay even after the client destroys them; the count is
released on setter replacement and on free, and a sweep reclaims destroyed
objects once nothing references them, so the tree stays bounded under churn.
The tree serializes to an ordered replay stream (constructors and setters
interleaved in the client's original issue order) for the recovery path.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
src/bin/e_wl_proxy/e_wl_proxy_tree.c | 669 +++++++++++++++++++++++++++++++++++
src/bin/e_wl_proxy/e_wl_proxy_tree.h | 88 +++++
2 files changed, 757 insertions(+)
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_tree.c b/src/bin/e_wl_proxy/e_wl_proxy_tree.c
new file mode 100644
index 000000000..a47cd9f14
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_tree.c
@@ -0,0 +1,669 @@
+#include "config.h"
+#include "e_wl_proxy.h"
+#include "e_wl_proxy_tree.h"
+#include "e_wl_proxy_idmap.h"
+#include "e_wl_proxy_xlate.h"
+#include "e_wl_proxy_protocol.h"
+#include "e_wl_proxy_wire.h"
+#include "e_wl_proxy_objmap.h"
+#include <Eina.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+typedef struct _Setter { uint16_t opcode; unsigned int seq; void *bytes; size_t len; int *fds; int nfds; } Setter;
+typedef struct _Global { uint32_t name; char iface[64]; } Global;
+
+struct _E_Wl_Proxy_Obj
+{
+ uint32_t id;
+ const struct wl_interface *iface;
+ void *ctor; /* malloc'd copy of constructor message */
+ size_t ctor_len;
+ int *ctor_fds; /* fds retained with the constructor */
+ int ctor_nfds;
+ Setter *setters; /* dynamic array, one per opcode seen */
+ int nsetters;
+ int refs; /* dependency refcount (0 = freeable) */
+ Eina_Bool destroyed; /* client destroyed it; kept while refs>0 */
+ unsigned int creation_seq;
+};
+
+struct _E_Wl_Proxy_Tree
+{
+ E_Wl_Proxy_Objmap *map;
+ unsigned int count;
+ unsigned int next_seq;
+ Global *globals;
+ int nglobals;
+ int globals_cap;
+};
+
+static void
+_fds_close(int *fds, int n)
+{ int i; for (i = 0; i < n; i++) if (fds[i] >= 0) close(fds[i]); free(fds); }
+
+static void
+_obj_free(void *p)
+{
+ E_Wl_Proxy_Obj *o = p;
+ int i;
+ if (!o) return;
+ for (i = 0; i < o->nsetters; i++)
+ { free(o->setters[i].bytes); _fds_close(o->setters[i].fds, o->setters[i].nfds); }
+ free(o->setters);
+ _fds_close(o->ctor_fds, o->ctor_nfds);
+ free(o->ctor);
+ free(o);
+}
+
+static void
+_unref_one(E_Wl_Proxy_Tree *t, uint32_t id)
+{
+ E_Wl_Proxy_Obj *o = e_wl_proxy_objmap_get(t->map, id);
+ if (o && o->refs > 0) o->refs--;
+}
+
+/* Reverse the dependency-refs a single retained message induced: for a
+ * constructor the sender is the new object's parent (bumped so it survives for
+ * replay), and any object-typed args were bumped too. `is_ctor` selects whether
+ * to release the parent bump. Mirrors the increments in e_wl_proxy_tree_request. */
+static void
+_unref_msg(E_Wl_Proxy_Tree *t, const void *bytes, size_t len, Eina_Bool is_ctor)
+{
+ E_Wl_Proxy_Hdr h;
+ E_Wl_Proxy_Obj *sender;
+
+ if (e_wl_proxy_wire_hdr(bytes, len, &h) != 1) return;
+ if (h.size < 8 || h.size > len) return;
+ if (is_ctor && h.object_id != 1) _unref_one(t, h.object_id); /* parent bump */
+ sender = e_wl_proxy_objmap_get(t->map, h.object_id);
+ if (sender && sender->iface && h.opcode < (uint16_t)sender->iface->method_count)
+ {
+ E_Wl_Proxy_Args a;
+ const struct wl_message *m = &sender->iface->methods[h.opcode];
+ if (e_wl_proxy_wire_args(m, (const char *)bytes + 8, h.size - 8, &a) == 0)
+ { int k; for (k = 0; k < a.n_obj_refs; k++) _unref_one(t, a.obj_refs[k]); }
+ }
+}
+
+/* Release every ref `o`'s retained constructor and setters hold on other
+ * objects. Call before freeing `o` so its dependencies can be reclaimed too. */
+static void
+_obj_unref_all(E_Wl_Proxy_Tree *t, E_Wl_Proxy_Obj *o)
+{
+ int s;
+ if (o->ctor && o->ctor_len) _unref_msg(t, o->ctor, o->ctor_len, EINA_TRUE);
+ for (s = 0; s < o->nsetters; s++)
+ if (o->setters[s].bytes) _unref_msg(t, o->setters[s].bytes, o->setters[s].len, EINA_FALSE);
+}
+
+/* Remove `o` from the tree: release its outgoing refs, unmap, free, drop count. */
+static void
+_obj_destroy(E_Wl_Proxy_Tree *t, E_Wl_Proxy_Obj *o)
+{
+ _obj_unref_all(t, o);
+ e_wl_proxy_objmap_del(t->map, o->id);
+ _obj_free(o);
+ if (t->count) t->count--;
+}
+
+/* Free objects the client already destroyed once nothing references them.
+ * Releasing one object's refs can make its dependencies freeable, so loop:
+ * collect the currently-freeable set (snapshot, since freeing mutates the map),
+ * destroy them, repeat until a pass finds none. */
+typedef struct _Sweep { E_Wl_Proxy_Obj **a; int n, cap; } _Sweep;
+static void
+_collect_freeable(uint32_t id EINA_UNUSED, void *node, void *data)
+{
+ _Sweep *s = data;
+ E_Wl_Proxy_Obj *o = node;
+ if (!o->destroyed || o->refs > 0) return;
+ if (s->n == s->cap)
+ {
+ int ncap = s->cap ? s->cap * 2 : 16;
+ E_Wl_Proxy_Obj **na = realloc(s->a, ncap * sizeof(*na));
+ if (!na) return; /* OOM: skip this round's overflow, caught next sweep */
+ s->a = na; s->cap = ncap;
+ }
+ s->a[s->n++] = o;
+}
+
+static void
+_sweep_destroyed(E_Wl_Proxy_Tree *t)
+{
+ for (;;)
+ {
+ _Sweep s = { NULL, 0, 0 };
+ int i;
+ e_wl_proxy_objmap_foreach(t->map, _collect_freeable, &s);
+ if (s.n == 0) { free(s.a); break; }
+ for (i = 0; i < s.n; i++) _obj_destroy(t, s.a[i]);
+ free(s.a);
+ }
+}
+
+E_Wl_Proxy_Tree *
+e_wl_proxy_tree_new(void)
+{
+ E_Wl_Proxy_Tree *t = calloc(1, sizeof(*t));
+ E_Wl_Proxy_Obj *disp;
+ if (!t) return NULL;
+ t->map = e_wl_proxy_objmap_new();
+ if (!t->map) { free(t); return NULL; }
+ disp = calloc(1, sizeof(*disp));
+ if (!disp) { e_wl_proxy_objmap_free(t->map, NULL); free(t); return NULL; }
+ disp->id = 1; disp->iface = e_wl_proxy_iface_display();
+ e_wl_proxy_objmap_set(t->map, 1, disp);
+ t->count = 1;
+ return t;
+}
+
+void
+e_wl_proxy_tree_free(E_Wl_Proxy_Tree *t)
+{
+ if (!t) return;
+ e_wl_proxy_objmap_free(t->map, _obj_free);
+ free(t->globals);
+ free(t);
+}
+
+/* Drop any retained setter (on any object) that references `id` in an object
+ * arg. Called when `id` is freed and reused for a different object: a setter
+ * like wl_surface.set_opaque_region/set_input_region holds the id of a region
+ * the client created, used (the compositor copies it) and then destroyed; the
+ * id is later reused (e.g. for a frame callback). Replaying that setter would
+ * name an object of the wrong type and make the new E drop the connection. */
+static void
+_drop_setters_referencing(uint32_t oid EINA_UNUSED, void *node, void *data)
+{
+ E_Wl_Proxy_Obj *o = node;
+ uint32_t freed = *(uint32_t *)data;
+ int s = 0;
+
+ if (!o || !o->iface) return;
+ while (s < o->nsetters)
+ {
+ Setter *st = &o->setters[s];
+ E_Wl_Proxy_Hdr h;
+ E_Wl_Proxy_Args args;
+ int hit = 0, k;
+
+ if (st->bytes &&
+ e_wl_proxy_wire_hdr(st->bytes, st->len, &h) == 1 &&
+ h.opcode < (uint16_t)o->iface->method_count &&
+ e_wl_proxy_wire_args(&o->iface->methods[h.opcode],
+ (const char *)st->bytes + 8, h.size - 8, &args) == 0)
+ for (k = 0; k < args.n_obj_refs; k++)
+ if (args.obj_refs[k] == freed) { hit = 1; break; }
+
+ if (hit)
+ {
+ free(st->bytes);
+ _fds_close(st->fds, st->nfds);
+ o->setters[s] = o->setters[o->nsetters - 1]; /* swap-remove */
+ o->nsetters--;
+ }
+ else s++;
+ }
+}
+
+static int *
+_fdq_take(E_Wl_Proxy_Fdq *q, int want, int *got)
+{
+ int *a; int i;
+ *got = 0;
+ if (!q || want <= 0) return NULL;
+ a = malloc(sizeof(int) * (size_t)want);
+ if (!a)
+ {
+ /* drop them to keep queue aligned */
+ for (i = 0; i < want; i++) { int fd = e_wl_proxy_fdq_pop(q); if (fd >= 0) close(fd); }
+ return NULL;
+ }
+ for (i = 0; i < want; i++) a[i] = e_wl_proxy_fdq_pop(q); /* may be -1 if underflow */
+ *got = want;
+ return a;
+}
+
+/* A "setter" that mutates state which OTHER objects depend on, so intermediate
+ * values must be preserved in order rather than deduplicated to the latest. The
+ * canonical case: wl_shm_pool.resize grows the pool, and a wl_buffer created
+ * between two resizes needs the pool at the intermediate size — keeping only the
+ * final resize and replaying it after the buffer's create_buffer would make E
+ * reject the (then-too-large) buffer. Such requests are appended (kept in full),
+ * not collapsed last-wins. Most setters are pure state and stay last-wins. */
+static Eina_Bool
+_is_ordered_setter(const struct wl_interface *iface, const struct wl_message *m)
+{
+ return (iface && m && !strcmp(iface->name, "wl_shm_pool") &&
+ !strcmp(m->name, "resize")) ? EINA_TRUE : EINA_FALSE;
+}
+
+int
+e_wl_proxy_tree_request(E_Wl_Proxy_Tree *t, const void *msg, size_t len,
+ E_Wl_Proxy_Fdq *q)
+{
+ E_Wl_Proxy_Hdr hdr;
+ E_Wl_Proxy_Obj *sender;
+ const struct wl_message *m;
+ E_Wl_Proxy_Args args;
+ int msg_nfds = 0;
+ int *msg_fds = NULL;
+
+ if (e_wl_proxy_wire_hdr(msg, len, &hdr) != 1) return -1;
+ if (hdr.size < 8 || hdr.size > len) return -1;
+
+ sender = e_wl_proxy_objmap_get(t->map, hdr.object_id);
+ if (!sender || !sender->iface) return -1;
+ if (hdr.opcode >= (uint16_t)sender->iface->method_count) return -1;
+ m = &sender->iface->methods[hdr.opcode];
+
+ if (e_wl_proxy_wire_args(m, (const char *)msg + 8, hdr.size - 8, &args) < 0)
+ return -1;
+
+ /* pop fd args for this message from the queue */
+ msg_fds = _fdq_take(q, args.fd_count, &msg_nfds);
+
+ {
+ int i;
+ for (i = 0; i < args.n_obj_refs; i++)
+ {
+ E_Wl_Proxy_Obj *ref = e_wl_proxy_objmap_get(t->map, args.obj_refs[i]);
+ if (ref) ref->refs++;
+ }
+ }
+
+ if (args.new_id)
+ {
+ const struct wl_interface *iface = args.new_iface;
+ E_Wl_Proxy_Obj *o;
+ if (args.has_generic_new_id)
+ iface = e_wl_proxy_iface_by_name(args.new_iface_name);
+ /* the new object's constructor is a request ON its parent (the sender);
+ * replaying it needs the parent alive. Keep the parent even if the
+ * client later destroys it — e.g. a wl_shm_pool destroyed after its
+ * wl_buffers are created (the buffers' create_buffer is sent on it). */
+ if (sender && sender->id != 1) sender->refs++;
+ o = calloc(1, sizeof(*o));
+ if (!o) { _fds_close(msg_fds, msg_nfds); return -1; }
+ o->id = args.new_id;
+ o->iface = iface;
+ o->creation_seq = ++t->next_seq;
+ o->ctor = malloc(hdr.size);
+ if (o->ctor) { memcpy(o->ctor, msg, hdr.size); o->ctor_len = hdr.size; }
+ /* attach fds to the constructor node */
+ o->ctor_fds = msg_fds; o->ctor_nfds = msg_nfds; msg_fds = NULL;
+ /* free any prior node at this id (a client reusing a live id) */
+ {
+ E_Wl_Proxy_Obj *old = e_wl_proxy_objmap_get(t->map, args.new_id);
+ if (old)
+ {
+ /* the id changes meaning: purge stale setters that named it */
+ if (old->refs > 0)
+ e_wl_proxy_objmap_foreach(t->map, _drop_setters_referencing, &args.new_id);
+ _obj_unref_all(t, old); /* release old's deps before discarding it */
+ _obj_free(old);
+ }
+ else t->count++;
+ }
+ e_wl_proxy_objmap_set(t->map, args.new_id, o);
+ _sweep_destroyed(t); /* old's released deps may now be reclaimable */
+ return 0;
+ }
+
+ if (!strcmp(m->name, "destroy"))
+ {
+ /* sender is the target object, already looked up and non-NULL above */
+ _fds_close(msg_fds, msg_nfds); msg_fds = NULL;
+ sender->destroyed = EINA_TRUE;
+ /* still referenced by a retained message (e.g. a region named by a live
+ * surface's set_opaque_region): keep it so replay can recreate it; it is
+ * reclaimed by _sweep_destroyed once that last reference is dropped. */
+ if (sender->refs > 0) return 0;
+ _obj_destroy(t, sender);
+ _sweep_destroyed(t); /* releasing sender's deps may free its parent */
+ return 0;
+ }
+
+ /* setter on the sender object: keep the latest message per opcode, tagged
+ * with a global op-sequence so replay can interleave it with constructors in
+ * the order the client actually issued things. Ordered setters (resize) are
+ * never collapsed — each occurrence is kept with its own sequence. */
+ {
+ E_Wl_Proxy_Obj *o = sender;
+ int i;
+ if (!_is_ordered_setter(o->iface, m))
+ for (i = 0; i < o->nsetters; i++)
+ if (o->setters[i].opcode == hdr.opcode)
+ {
+ /* this message's obj-refs were already bumped above; release the
+ * superseded setter's refs so a now-unreferenced object (e.g. the
+ * previously-attached wl_buffer) can be reclaimed */
+ _unref_msg(t, o->setters[i].bytes, o->setters[i].len, EINA_FALSE);
+ free(o->setters[i].bytes);
+ _fds_close(o->setters[i].fds, o->setters[i].nfds);
+ o->setters[i].fds = msg_fds; o->setters[i].nfds = msg_nfds; msg_fds = NULL;
+ o->setters[i].bytes = malloc(hdr.size);
+ if (!o->setters[i].bytes) { o->setters[i].len = 0; return -1; }
+ memcpy(o->setters[i].bytes, msg, hdr.size); o->setters[i].len = hdr.size;
+ o->setters[i].seq = ++t->next_seq;
+ _sweep_destroyed(t); /* old setter's target may now be freeable */
+ return 0;
+ }
+ {
+ Setter *ns = realloc(o->setters, (o->nsetters + 1) * sizeof(Setter));
+ if (!ns) { _fds_close(msg_fds, msg_nfds); return -1; }
+ o->setters = ns;
+ o->setters[o->nsetters].opcode = hdr.opcode;
+ o->setters[o->nsetters].fds = msg_fds; o->setters[o->nsetters].nfds = msg_nfds; msg_fds = NULL;
+ o->setters[o->nsetters].bytes = malloc(hdr.size);
+ if (!o->setters[o->nsetters].bytes)
+ { /* slot uncommitted (nsetters not bumped): its fds won't be freed
+ * by _obj_free, so close them here to avoid an fd leak */
+ _fds_close(o->setters[o->nsetters].fds, o->setters[o->nsetters].nfds);
+ return -1;
+ }
+ memcpy(o->setters[o->nsetters].bytes, msg, hdr.size);
+ o->setters[o->nsetters].len = hdr.size;
+ o->setters[o->nsetters].seq = ++t->next_seq;
+ o->nsetters++;
+ }
+ }
+ _fds_close(msg_fds, msg_nfds);
+ return 0;
+}
+
+int
+e_wl_proxy_tree_obj_setter_count(E_Wl_Proxy_Tree *t, uint32_t id)
+{
+ E_Wl_Proxy_Obj *o = t ? e_wl_proxy_objmap_get(t->map, id) : NULL;
+ return o ? o->nsetters : -1;
+}
+
+const char *
+e_wl_proxy_tree_obj_iface(E_Wl_Proxy_Tree *t, uint32_t id)
+{
+ E_Wl_Proxy_Obj *o = t ? e_wl_proxy_objmap_get(t->map, id) : NULL;
+ return (o && o->iface) ? o->iface->name : NULL;
+}
+
+unsigned int
+e_wl_proxy_tree_count(E_Wl_Proxy_Tree *t) { return t ? t->count : 0; }
+
+static void
+_global_set(E_Wl_Proxy_Tree *t, uint32_t name, const char *iface, size_t ilen)
+{
+ int i;
+ for (i = 0; i < t->nglobals; i++)
+ if (t->globals[i].name == name) break; /* last-wins on reuse */
+ if (i == t->nglobals)
+ {
+ /* a real compositor advertises tens of globals; cap the table so a
+ * hostile/buggy peer can't drive unbounded allocation */
+ if (t->nglobals >= 4096) return;
+ if (t->nglobals == t->globals_cap)
+ {
+ int ncap = t->globals_cap ? t->globals_cap * 2 : 16;
+ Global *ng = realloc(t->globals, ncap * sizeof(Global));
+ if (!ng) return;
+ t->globals = ng; t->globals_cap = ncap;
+ }
+ t->nglobals++;
+ }
+ t->globals[i].name = name;
+ if (ilen >= sizeof(t->globals[i].iface)) ilen = sizeof(t->globals[i].iface) - 1;
+ memcpy(t->globals[i].iface, iface, ilen);
+ t->globals[i].iface[ilen] = '\0';
+}
+
+static void
+_global_del(E_Wl_Proxy_Tree *t, uint32_t name)
+{
+ int i;
+ for (i = 0; i < t->nglobals; i++)
+ if (t->globals[i].name == name)
+ {
+ t->globals[i] = t->globals[t->nglobals - 1]; /* swap-remove */
+ t->nglobals--;
+ return;
+ }
+}
+
+int
+e_wl_proxy_tree_event(E_Wl_Proxy_Tree *t, const void *msg, size_t len)
+{
+ E_Wl_Proxy_Hdr hdr;
+ E_Wl_Proxy_Obj *sender;
+ const struct wl_message *ev;
+ const unsigned char *body;
+ size_t blen;
+
+ if (e_wl_proxy_wire_hdr(msg, len, &hdr) != 1) return -1;
+ if (hdr.size < 8 || hdr.size > len) return -1;
+ sender = e_wl_proxy_objmap_get(t->map, hdr.object_id);
+ if (!sender || !sender->iface) return -1;
+ if (hdr.opcode >= (uint16_t)sender->iface->event_count) return 0;
+ ev = &sender->iface->events[hdr.opcode];
+ body = (const unsigned char *)msg + 8;
+ blen = hdr.size - 8;
+
+ if (!strcmp(sender->iface->name, "wl_registry") && !strcmp(ev->name, "global"))
+ {
+ uint32_t name, slen;
+ if (blen < 8) return -1;
+ memcpy(&name, body, 4);
+ memcpy(&slen, body + 4, 4);
+ if (slen == 0 || (size_t)8 + slen > blen) return -1;
+ _global_set(t, name, (const char *)body + 8, slen - 1);
+ return 0;
+ }
+ if (!strcmp(sender->iface->name, "wl_registry") && !strcmp(ev->name, "global_remove"))
+ {
+ uint32_t name;
+ if (blen < 4) return -1;
+ memcpy(&name, body, 4);
+ _global_del(t, name);
+ return 0;
+ }
+ return 0;
+}
+
+const char *
+e_wl_proxy_tree_global_iface(E_Wl_Proxy_Tree *t, uint32_t name)
+{
+ int i;
+ if (!t) return NULL;
+ for (i = 0; i < t->nglobals; i++)
+ if (t->globals[i].name == name) return t->globals[i].iface;
+ return NULL;
+}
+
+uint32_t
+e_wl_proxy_tree_global_by_iface(E_Wl_Proxy_Tree *t, const char *iface)
+{
+ int i;
+ if (!t || !iface) return 0;
+ for (i = 0; i < t->nglobals; i++)
+ if (!strcmp(t->globals[i].iface, iface)) return t->globals[i].name;
+ return 0;
+}
+
+int
+e_wl_proxy_tree_remap_bind_iface(E_Wl_Proxy_Tree *new_t, void *bind_msg, size_t len)
+{
+ E_Wl_Proxy_Hdr hdr;
+ unsigned char *p = bind_msg;
+ uint32_t slen, newname;
+ char iface[64];
+ size_t cp;
+
+ if (!new_t) return -1;
+ if (e_wl_proxy_wire_hdr(bind_msg, len, &hdr) != 1) return -1;
+ /* need header(8) + name(4) + string-length(4) at least */
+ if (hdr.size < 16 || hdr.size > len) return -1;
+ memcpy(&slen, p + 12, 4); /* "usun": [name][slen][iface..] */
+ if (slen == 0 || (size_t)16 + slen > hdr.size) return -1;
+ cp = slen - 1; /* drop the trailing NUL */
+ if (cp >= sizeof(iface)) cp = sizeof(iface) - 1;
+ memcpy(iface, p + 16, cp); iface[cp] = '\0';
+
+ newname = e_wl_proxy_tree_global_by_iface(new_t, iface);
+ if (!newname) return -1; /* unknown here: leave unchanged */
+ memcpy(p + 8, &newname, 4); /* rewrite the global name */
+ return 0;
+}
+
+/* One replay message (a constructor or a retained setter), tagged with the
+ * global op-sequence at which the client issued it. */
+typedef struct _Unit
+{
+ unsigned int seq;
+ const void *bytes;
+ size_t len;
+ const int *fds;
+ int nfds;
+} _Unit;
+typedef struct _UColl { _Unit *u; int n, cap; int oom; } _UColl;
+
+static void
+_ucoll_add(_UColl *c, unsigned int seq, const void *bytes, size_t len,
+ const int *fds, int nfds)
+{
+ if (!bytes || !len) return;
+ if (c->n == c->cap)
+ {
+ int ncap = c->cap ? c->cap * 2 : 64;
+ _Unit *nu = realloc(c->u, ncap * sizeof(_Unit));
+ if (!nu) { c->oom = 1; return; }
+ c->u = nu; c->cap = ncap;
+ }
+ c->u[c->n].seq = seq; c->u[c->n].bytes = bytes; c->u[c->n].len = len;
+ c->u[c->n].fds = fds; c->u[c->n].nfds = nfds;
+ c->n++;
+}
+
+static void
+_collect_units(uint32_t id EINA_UNUSED, void *node, void *data)
+{
+ _UColl *c = data;
+ E_Wl_Proxy_Obj *o = node;
+ int s;
+ if (!o->ctor || !o->ctor_len) return; /* no constructor (wl_display): skip */
+ _ucoll_add(c, o->creation_seq, o->ctor, o->ctor_len, o->ctor_fds, o->ctor_nfds);
+ for (s = 0; s < o->nsetters; s++)
+ _ucoll_add(c, o->setters[s].seq, o->setters[s].bytes, o->setters[s].len,
+ o->setters[s].fds, o->setters[s].nfds);
+}
+
+static int
+_unit_cmp(const void *a, const void *b)
+{
+ const _Unit *ua = a, *ub = b;
+ return (ua->seq > ub->seq) - (ua->seq < ub->seq);
+}
+
+/* Build the whole tree as one list of replay messages (every constructor and
+ * retained setter) sorted by the global op-sequence — i.e. the exact order the
+ * client originally issued them. Returns the sorted array (caller frees) and
+ * sets *out_n to the count; *out_n is 0 for an empty tree (returns NULL) or -1
+ * on allocation failure. */
+static _Unit *
+_tree_order(E_Wl_Proxy_Tree *t, int *out_n)
+{
+ _UColl c = { NULL, 0, 0, 0 };
+ e_wl_proxy_objmap_foreach(t->map, _collect_units, &c);
+ if (c.oom) { free(c.u); *out_n = -1; return NULL; }
+ qsort(c.u, c.n, sizeof(_Unit), _unit_cmp);
+ *out_n = c.n;
+ return c.u;
+}
+
+int
+e_wl_proxy_tree_serialize(E_Wl_Proxy_Tree *t, Eina_Binbuf *out)
+{
+ _Unit *u; int n, i;
+
+ if (!t || !out) return -1;
+ u = _tree_order(t, &n);
+ if (n < 0) return -1;
+ for (i = 0; i < n; i++)
+ eina_binbuf_append_length(out, (const unsigned char *)u[i].bytes, u[i].len);
+ free(u);
+ return 0;
+}
+
+int
+e_wl_proxy_tree_serialize_cb(E_Wl_Proxy_Tree *t,
+ void (*emit)(const void *, size_t, const int *, int, void *), void *data)
+{
+ _Unit *u; int n, i;
+
+ if (!t || !emit) return -1;
+ u = _tree_order(t, &n);
+ if (n < 0) return -1;
+ for (i = 0; i < n; i++)
+ emit(u[i].bytes, u[i].len, u[i].fds, u[i].nfds, data);
+ free(u);
+ return 0;
+}
+
+int
+e_wl_proxy_tree_replay(E_Wl_Proxy_Tree *t, E_Wl_Proxy_Idmap *map,
+ void (*emit)(const void *, size_t, const int *, int, void *), void *data)
+{
+ _Unit *u; int n, i;
+
+ if (!t || !map || !emit) return -1;
+ u = _tree_order(t, &n);
+ if (n < 0) return -1;
+
+ /* Single pass in client-issue order. Translating a constructor allocates and
+ * records its backend id, so anything referencing it later resolves; and
+ * since no object can be referenced before it was created, every message's
+ * dependencies are already mapped by the time it is emitted. This also lands
+ * wl_shm_pool.resize before the create_buffer that needs the grown pool, and
+ * wl_surface.commit after the attach/damage of the same frame. */
+ for (i = 0; i < n; i++)
+ {
+ unsigned char stackbuf[E_WL_PROXY_BUF_SIZE];
+ unsigned char *tmp = stackbuf, *heap = NULL;
+ /* a wire message can be up to 65535 bytes (16-bit size); translate in a
+ * heap buffer when it exceeds the stack scratch rather than skipping it */
+ if (u[i].len > sizeof(stackbuf))
+ {
+ heap = malloc(u[i].len);
+ if (!heap) continue; /* OOM: skip */
+ tmp = heap;
+ }
+ memcpy(tmp, u[i].bytes, u[i].len);
+ if (e_wl_proxy_xlate(tmp, u[i].len, map, EINA_TRUE) == 0)
+ emit(tmp, u[i].len, u[i].fds, u[i].nfds, data);
+ /* else: unmapped ref (stale/destroyed) — drop this message */
+ free(heap);
+ }
+ free(u);
+ return 0;
+}
+
+int
+e_wl_proxy_tree_remap_bind(E_Wl_Proxy_Tree *old_t, E_Wl_Proxy_Tree *new_t,
+ void *bind_msg, size_t len)
+{
+ E_Wl_Proxy_Hdr hdr;
+ unsigned char *body;
+ uint32_t old_name, new_name;
+ const char *iface;
+
+ if (e_wl_proxy_wire_hdr(bind_msg, len, &hdr) != 1) return -1;
+ if (hdr.size < 12 || hdr.size > len) return -1; /* header + at least the name u32 */
+ body = (unsigned char *)bind_msg + 8;
+
+ memcpy(&old_name, body, 4);
+ iface = e_wl_proxy_tree_global_iface(old_t, old_name);
+ if (!iface) return -1;
+ new_name = e_wl_proxy_tree_global_by_iface(new_t, iface);
+ if (!new_name) return -1;
+ memcpy(body, &new_name, 4); /* rewrite the name in place */
+ return 0;
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_tree.h b/src/bin/e_wl_proxy/e_wl_proxy_tree.h
new file mode 100644
index 000000000..5a63d4b86
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_tree.h
@@ -0,0 +1,88 @@
+#ifndef E_WL_PROXY_TREE_H
+#define E_WL_PROXY_TREE_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <wayland-util.h>
+#include "e_wl_proxy_fdq.h"
+#include "e_wl_proxy_idmap.h"
+
+typedef struct _E_Wl_Proxy_Tree E_Wl_Proxy_Tree;
+typedef struct _E_Wl_Proxy_Obj E_Wl_Proxy_Obj;
+
+E_Wl_Proxy_Tree *e_wl_proxy_tree_new(void);
+void e_wl_proxy_tree_free(E_Wl_Proxy_Tree *t);
+
+/* Feed one complete client->server request (full message: 8-byte header + body).
+ * If `q` is non-NULL, the message's fd args (fd_count, computed from the
+ * protocol signature) are popped from the queue in order and retained on the
+ * resulting object node (closed when the node or setter slot is freed/replaced).
+ * Pass q = NULL to skip fd retention. Returns 0 on success, -1 if the message
+ * is malformed or references an unknown object. */
+int e_wl_proxy_tree_request(E_Wl_Proxy_Tree *t, const void *msg, size_t len,
+ E_Wl_Proxy_Fdq *q);
+
+/* test/query: interface name of a live object, or NULL if absent. */
+const char *e_wl_proxy_tree_obj_iface(E_Wl_Proxy_Tree *t, uint32_t id);
+/* test/query: number of live objects. */
+unsigned int e_wl_proxy_tree_count(E_Wl_Proxy_Tree *t);
+/* test/query: number of distinct retained setters on an object, or -1 if absent. */
+int e_wl_proxy_tree_obj_setter_count(E_Wl_Proxy_Tree *t, uint32_t id);
+
+#include <Eina.h>
+
+/* Serialize the whole tree to a client->server replay byte stream appended to
+ * `out`: every object (in creation order) as its constructor message followed
+ * by its retained setter messages. wl_display (no constructor) is skipped.
+ * Returns 0 on success, -1 on allocation failure. */
+int e_wl_proxy_tree_serialize(E_Wl_Proxy_Tree *t, Eina_Binbuf *out);
+
+/* Serialize the tree as an ordered sequence of replay segments, invoking `emit`
+ * once per message in creation order: ctor then retained setters for each object
+ * (wl_display skipped). `bytes`/`len` is the message; `fds`/`nfds` are the fds
+ * to re-attach (may be 0). Pointers are valid only for the duration of the call.
+ * Returns 0 on success, -1 on allocation failure. */
+int e_wl_proxy_tree_serialize_cb(E_Wl_Proxy_Tree *t,
+ void (*emit)(const void *bytes, size_t len,
+ const int *fds, int nfds, void *data),
+ void *data);
+
+/* Replay the whole tree as a client->backend byte stream translated through a
+ * FRESH `map` (pass a newly-created idmap seeded only with wl_display). Each
+ * object's constructor is emitted in creation order (translating its new_id
+ * allocates a dense backend id and records the mapping), then all retained
+ * setters (translated; a setter referencing a since-destroyed object fails
+ * translation and is silently dropped). On return `map` holds the full
+ * client<->backend mapping for the recovered connection. `emit` is called once
+ * per message with the translated bytes + fds (valid only for the call).
+ * Returns 0 on success, -1 on allocation failure. */
+int e_wl_proxy_tree_replay(E_Wl_Proxy_Tree *t, E_Wl_Proxy_Idmap *map,
+ void (*emit)(const void *bytes, size_t len,
+ const int *fds, int nfds, void *data),
+ void *data);
+
+/* Feed one complete server->client event. Captures wl_registry globals.
+ * Returns 0 on success, -1 if malformed. */
+int e_wl_proxy_tree_event(E_Wl_Proxy_Tree *t, const void *msg, size_t len);
+/* test/query: interface name for a global numeric name, or NULL. */
+const char *e_wl_proxy_tree_global_iface(E_Wl_Proxy_Tree *t, uint32_t name);
+/* test/query: the global numeric name advertising `iface`, or 0 if none. */
+uint32_t e_wl_proxy_tree_global_by_iface(E_Wl_Proxy_Tree *t, const char *iface);
+
+/* Rewrite the `name` field of a wl_registry.bind message (in place) so it
+ * targets the same interface in `new_t`'s globals as it did in `old_t`'s.
+ * `bind_msg` must be a complete bind message (8-byte header + body; the body's
+ * first u32 is the global name). Returns 0 on success, -1 if the interface
+ * can't be matched (caller should drop the client). */
+int e_wl_proxy_tree_remap_bind(E_Wl_Proxy_Tree *old_t, E_Wl_Proxy_Tree *new_t,
+ void *bind_msg, size_t len);
+
+/* Rewrite a wl_registry.bind's `name` (in place) to `new_t`'s global for the
+ * interface named in the bind's own expanded "usun" body — i.e. remap the bind
+ * to the restarted compositor's global numbering without needing the old one.
+ * Returns 0 if rewritten, -1 if the interface isn't in new_t (bind left as-is).
+ * Note: if several globals share an interface (e.g. multiple wl_output), this
+ * targets the first match, so multi-instance globals are not disambiguated. */
+int e_wl_proxy_tree_remap_bind_iface(E_Wl_Proxy_Tree *new_t, void *bind_msg, size_t len);
+
+#endif
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.