This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch feat/e-wl-proxy-planb
in repository enlightenment.
View the commit online.
commit ee2ff1e0717b3e242d41e6c60dc509aff1ab0ad6
Author: [email protected] <[email protected]>
AuthorDate: Sat Jun 13 11:41:14 2026 -0600
feat(wl_proxy): module mirroring window WM-state for crash recovery
A companion module that runs inside Enlightenment when behind the proxy. It
connects to the proxy's control socket and mirrors each session-recovery
window's WM-state (geometry, desktop, stacking order, focus) to the proxy's
crash-safe store as it changes, keyed by the window's stable uuid.
On E_CLIENT_HOOK_UUID_SET it restores a recovered window's state from the
store, replaying stacking and focus in the captured order. The control socket
is non-blocking so a wedged proxy can never freeze Enlightenment. Enabled by
the wl-proxy build option (default on).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
meson_options.txt | 4 +
src/modules/meson.build | 1 +
src/modules/wl_proxy/e_mod_main.c | 370 +++++++++++++++++++++++++++++
src/modules/wl_proxy/e_wl_proxy_recv.c | 42 ++++
src/modules/wl_proxy/e_wl_proxy_recv.h | 15 ++
src/modules/wl_proxy/e_wl_proxy_wm.h | 29 +++
src/modules/wl_proxy/e_wl_proxy_wm_codec.c | 48 ++++
src/modules/wl_proxy/meson.build | 9 +
8 files changed, 518 insertions(+)
diff --git a/meson_options.txt b/meson_options.txt
index 70b30bb30..e7d81b7cc 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -98,6 +98,10 @@ option('wl-weekeyboard',
type: 'boolean',
value: true,
description: 'enable internal wayland vkbd: (default=true)')
+option('wl-proxy',
+ type: 'boolean',
+ value: true,
+ description: 'enable wayland proxy module: (default=true)')
option('systemd',
type: 'boolean',
diff --git a/src/modules/meson.build b/src/modules/meson.build
index 68dfc263a..ad2821293 100644
--- a/src/modules/meson.build
+++ b/src/modules/meson.build
@@ -70,6 +70,7 @@ mods = [
'wl_text_input',
'wl_desktop_shell',
'wl_weekeyboard',
+ 'wl_proxy',
### XXX: disabled for now
# 'wl_fb'
'pants',
diff --git a/src/modules/wl_proxy/e_mod_main.c b/src/modules/wl_proxy/e_mod_main.c
new file mode 100644
index 000000000..739295e84
--- /dev/null
+++ b/src/modules/wl_proxy/e_mod_main.c
@@ -0,0 +1,370 @@
+#include "e.h"
+#include "e_wl_proxy_wm.h"
+#include "e_wl_proxy_proto.h"
+#include "e_wl_proxy_recv.h"
+#include <Ecore.h>
+#include <Eina.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+E_API E_Module_Api e_modapi = { E_MODULE_API_VERSION, "WL Proxy" };
+
+static int _ctl_fd = -1;
+static Eina_List *_handlers = NULL;
+/* monotonic; supplies stack_seq/focus_seq. Persists across a module reload
+ * (E never dlclose()s the DSO) — harmless, since the proxy resets on our BYE. */
+static unsigned int _seq = 0;
+
+static Ecore_Fd_Handler *_ctl_h = NULL;
+static Eina_Binbuf *_inbuf = NULL;
+static Eina_Hash *_states = NULL; /* uuid -> E_Wl_Proxy_Wm_State* */
+static E_Client_Hook *_uuid_hook = NULL;
+
+typedef struct _Restored
+{
+ E_Client *ec;
+ unsigned int stack_seq;
+ unsigned int focus_seq;
+} Restored;
+
+static Eina_List *_restored = NULL; /* Restored* in arrival order */
+
+static int
+_ctl_connect(void)
+{
+ const char *runtime = getenv("XDG_RUNTIME_DIR");
+ const char *name = getenv("E_WL_PROXY_SOCK");
+ struct sockaddr_un addr;
+ char path[256];
+ int fd, n;
+
+ if (!runtime || !name || !name[0]) return -1;
+ n = snprintf(path, sizeof(path), "%s/%s", runtime, name);
+ if (n <= 0 || (size_t)n >= sizeof(path)) return -1;
+ if (strlen(path) >= sizeof(addr.sun_path)) return -1;
+
+ fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0) return -1;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, path);
+ /* Non-blocking: a wedged proxy must never block E's main loop. AF_UNIX
+ * stream connect completes synchronously, so this returns 0 or fails. */
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return -1; }
+ return fd;
+}
+
+/* Send one control frame. Returns 0 on success, -1 on error (drops the conn). */
+static int
+_ctl_send(E_Wl_Proxy_Ctl_Type type, const char *uuid, const void *blob, uint32_t blob_len)
+{
+ E_Wl_Proxy_Ctl_Hdr hdr;
+ uint32_t uuid_len = uuid ? (uint32_t)strlen(uuid) : 0;
+ struct iovec iov[3];
+ struct msghdr msg;
+ size_t total;
+ int n = 0;
+ ssize_t r;
+
+ if (_ctl_fd < 0) return -1;
+ if (uuid_len > E_WL_PROXY_CTL_MAX_UUID) return -1; /* keep within proxy limits */
+ if (blob_len > E_WL_PROXY_CTL_MAX_BLOB) return -1;
+ hdr.type = (uint32_t)type;
+ hdr.uuid_len = uuid_len;
+ hdr.blob_len = blob_len;
+ total = sizeof(hdr) + uuid_len + blob_len;
+
+ iov[n].iov_base = &hdr; iov[n].iov_len = sizeof(hdr); n++;
+ if (uuid_len) { iov[n].iov_base = (void *)uuid; iov[n].iov_len = uuid_len; n++; }
+ if (blob_len) { iov[n].iov_base = (void *)blob; iov[n].iov_len = blob_len; n++; }
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = n;
+ do { r = sendmsg(_ctl_fd, &msg, MSG_NOSIGNAL); } while (r < 0 && errno == EINTR);
+
+ if (r == (ssize_t)total) return 0; /* whole frame out */
+ if ((r < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
+ return -1; /* transient backpressure: drop this frame, keep the fd; the
+ * next event re-sends current state (idempotent in the store) */
+ /* partial send desyncs the framing, or a hard error: abandon the connection
+ * (mirroring goes dark until the module reloads — no reconnect by design). */
+ close(_ctl_fd); _ctl_fd = -1;
+ return -1;
+}
+
+static void
+_snapshot(E_Client *ec, E_Wl_Proxy_Wm_State *st)
+{
+ memset(st, 0, sizeof(*st));
+ st->x = ec->x; st->y = ec->y; st->w = ec->w; st->h = ec->h;
+ if (ec->desk) { st->desk_x = ec->desk->x; st->desk_y = ec->desk->y; }
+ if (ec->zone) st->zone = (int)ec->zone->num;
+ st->layer = (int)ec->layer;
+ st->maximized = (unsigned int)ec->maximized;
+ st->fullscreen = ec->fullscreen ? 1 : 0;
+ st->sticky = ec->sticky ? 1 : 0;
+ st->shaded = ec->shaded ? 1 : 0;
+ st->iconified = ec->iconic ? 1 : 0;
+}
+
+/* Encode and send the client's current WM state. bump_stack/bump_focus assign
+ * the next monotonic sequence so the consumer can restore z-order/focus. */
+static void
+_mirror(E_Client *ec, Eina_Bool bump_stack, Eina_Bool bump_focus)
+{
+ E_Wl_Proxy_Wm_State st;
+ void *blob;
+ int size = 0;
+
+ if (!ec || !ec->uuid) return; /* only session-recovery windows */
+ if (_ctl_fd < 0) return;
+
+ _snapshot(ec, &st);
+ if (bump_stack) st.stack_seq = ++_seq;
+ if (bump_focus) st.focus_seq = ++_seq;
+
+ blob = e_wl_proxy_wm_encode(&st, &size);
+ if (!blob || size <= 0) { free(blob); return; }
+ _ctl_send(E_WL_PROXY_CTL_WM_STATE, ec->uuid, blob, (uint32_t)size);
+ free(blob);
+}
+
+static Eina_Bool
+_cb_property(void *d EINA_UNUSED, int t EINA_UNUSED, void *ev)
+{
+ E_Event_Client_Property *e = ev; /* PROPERTY carries an extra field; use its type */
+ _mirror(e->ec, EINA_FALSE, EINA_FALSE);
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool
+_cb_stack(void *d EINA_UNUSED, int t EINA_UNUSED, void *ev)
+{
+ E_Event_Client *e = ev;
+ _mirror(e->ec, EINA_TRUE, EINA_FALSE);
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool
+_cb_focus_in(void *d EINA_UNUSED, int t EINA_UNUSED, void *ev)
+{
+ E_Event_Client *e = ev;
+ _mirror(e->ec, EINA_FALSE, EINA_TRUE);
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+/* window moved or resized: capture the new geometry. E emits MOVE/RESIZE as their
+ * own events (not PROPERTY), so without these the store keeps a stale position. */
+static Eina_Bool
+_cb_geom(void *d EINA_UNUSED, int t EINA_UNUSED, void *ev)
+{
+ E_Event_Client *e = ev;
+ _mirror(e->ec, EINA_FALSE, EINA_FALSE);
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool
+_cb_remove(void *d EINA_UNUSED, int t EINA_UNUSED, void *ev)
+{
+ E_Event_Client *e = ev;
+ /* normal close while E is alive: drop the blob (a crash sends nothing,
+ * so the blob is retained and later restored). */
+ if (e->ec && e->ec->uuid && (_ctl_fd >= 0))
+ _ctl_send(E_WL_PROXY_CTL_WM_FORGET, e->ec->uuid, NULL, 0);
+ {
+ Eina_List *l;
+ Restored *r;
+ EINA_LIST_FOREACH(_restored, l, r)
+ if (r->ec == e->ec) { _restored = eina_list_remove_list(_restored, l); free(r); break; }
+ }
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static void
+_state_free(void *d)
+{
+ free(d); /* E_Wl_Proxy_Wm_State, malloc'd by the codec decode */
+}
+
+static void
+_drain_inbuf(void)
+{
+ for (;;)
+ {
+ char *uuid = NULL;
+ E_Wl_Proxy_Wm_State *st = NULL;
+ size_t consumed = 0;
+ const unsigned char *b = eina_binbuf_string_get(_inbuf);
+ size_t len = eina_binbuf_length_get(_inbuf);
+ int r = e_wl_proxy_recv_frame(b, len, &uuid, &st, &consumed);
+ if (r == 0) break; /* need more */
+ if (r < 0) { eina_binbuf_reset(_inbuf); break; } /* malformed: drop */
+ if (uuid && st)
+ {
+ /* eina_hash_set returns the prior value WITHOUT calling the
+ * free_cb, so free it ourselves to avoid leaking on updates. */
+ E_Wl_Proxy_Wm_State *old = eina_hash_set(_states, uuid, st);
+ free(old);
+ }
+ else free(st);
+ free(uuid);
+ eina_binbuf_remove(_inbuf, 0, consumed);
+ }
+}
+
+static Eina_Bool
+_ctl_read_cb(void *d EINA_UNUSED, Ecore_Fd_Handler *fdh)
+{
+ char buf[4096];
+ ssize_t n;
+
+ if (ecore_main_fd_handler_active_get(fdh, ECORE_FD_ERROR)) goto dead;
+ n = recv(_ctl_fd, buf, sizeof(buf), 0);
+ if (n == 0) goto dead;
+ if (n < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return ECORE_CALLBACK_RENEW;
+ goto dead;
+ }
+ eina_binbuf_append_length(_inbuf, (const unsigned char *)buf, (size_t)n);
+ _drain_inbuf();
+ return ECORE_CALLBACK_RENEW;
+
+dead:
+ ecore_main_fd_handler_del(_ctl_h); _ctl_h = NULL;
+ /* close the fd too: leaving it open makes every later window event try (and
+ * fail) a sendmsg on a dead socket before _ctl_send finally closes it. */
+ if (_ctl_fd >= 0) { close(_ctl_fd); _ctl_fd = -1; }
+ return ECORE_CALLBACK_CANCEL;
+}
+
+static int
+_by_stack(const void *a, const void *b)
+{
+ const Restored *ra = a, *rb = b;
+ return (ra->stack_seq > rb->stack_seq) - (ra->stack_seq < rb->stack_seq);
+}
+
+/* Re-stack all restored windows by ascending stack_seq (raising each in turn
+ * leaves the highest seq on top), then focus the max focus_seq. Called after
+ * each window is restored; converges as windows arrive. */
+static void
+_reorder(void)
+{
+ Eina_List *sorted, *l;
+ Restored *r, *focus_win = NULL;
+ unsigned int best_focus = 0;
+
+ sorted = eina_list_clone(_restored);
+ sorted = eina_list_sort(sorted, eina_list_count(sorted), _by_stack);
+ EINA_LIST_FOREACH(sorted, l, r)
+ {
+ if (r->ec && r->ec->frame) evas_object_raise(r->ec->frame);
+ if (r->focus_seq >= best_focus) { best_focus = r->focus_seq; focus_win = r; }
+ }
+ eina_list_free(sorted);
+
+ if (focus_win && focus_win->ec && (best_focus > 0))
+ e_client_activate(focus_win->ec, EINA_TRUE);
+}
+
+static void
+_apply(E_Client *ec, const E_Wl_Proxy_Wm_State *st)
+{
+ /* geometry (frame coords) — overrides E_Remember's possibly-stale baseline.
+ * For a maximized window this is the pre-maximize geometry; e_client_maximize
+ * below recomputes the maximized geometry, so this is harmlessly overridden. */
+ if (ec->frame)
+ evas_object_geometry_set(ec->frame, st->x, st->y, st->w, st->h);
+
+ /* virtual desktop */
+ if (ec->zone)
+ {
+ E_Desk *desk = e_desk_at_xy_get(ec->zone, st->desk_x, st->desk_y);
+ if (desk) e_client_desk_set(ec, desk);
+ }
+
+ /* maximize / fullscreen / shade / iconify */
+ if (st->maximized) e_client_maximize(ec, (E_Maximize)st->maximized);
+ if (st->fullscreen) e_client_fullscreen(ec, E_FULLSCREEN_RESIZE);
+ if (st->shaded) e_client_shade(ec, E_DIRECTION_UP);
+ if (st->iconified) e_client_iconify(ec);
+}
+
+static void
+_uuid_set_hook(void *d EINA_UNUSED, E_Client *ec)
+{
+ E_Wl_Proxy_Wm_State *st;
+ Restored *r;
+
+ if (!ec || !ec->uuid || !_states) return;
+ st = eina_hash_find(_states, ec->uuid);
+ if (!st) return;
+ _apply(ec, st);
+
+ r = calloc(1, sizeof(*r));
+ if (!r) return;
+ r->ec = ec; r->stack_seq = st->stack_seq; r->focus_seq = st->focus_seq;
+ _restored = eina_list_append(_restored, r);
+ /* snapshot consumed: drop it so a second hook fire is a no-op and the blob
+ * is freed (the window is now live and re-mirrored by the producer). */
+ eina_hash_del(_states, ec->uuid, NULL);
+ _reorder();
+}
+
+E_API void *
+e_modapi_init(E_Module *m)
+{
+ e_wl_proxy_wm_codec_init();
+ _ctl_fd = _ctl_connect();
+ if (_ctl_fd >= 0) _ctl_send(E_WL_PROXY_CTL_HELLO, NULL, NULL, 0);
+ _states = eina_hash_string_superfast_new(_state_free);
+ if (_ctl_fd >= 0)
+ {
+ _inbuf = eina_binbuf_new();
+ _ctl_h = ecore_main_fd_handler_add(_ctl_fd, ECORE_FD_READ | ECORE_FD_ERROR,
+ _ctl_read_cb, NULL, NULL, NULL);
+ }
+ _uuid_hook = e_client_hook_add(E_CLIENT_HOOK_UUID_SET, _uuid_set_hook, NULL);
+ /* a missing proxy is not fatal: the module simply mirrors nothing */
+ E_LIST_HANDLER_APPEND(_handlers, E_EVENT_CLIENT_PROPERTY, _cb_property, NULL);
+ E_LIST_HANDLER_APPEND(_handlers, E_EVENT_CLIENT_MOVE, _cb_geom, NULL);
+ E_LIST_HANDLER_APPEND(_handlers, E_EVENT_CLIENT_RESIZE, _cb_geom, NULL);
+ E_LIST_HANDLER_APPEND(_handlers, E_EVENT_CLIENT_STACK, _cb_stack, NULL);
+ E_LIST_HANDLER_APPEND(_handlers, E_EVENT_CLIENT_FOCUS_IN, _cb_focus_in, NULL);
+ E_LIST_HANDLER_APPEND(_handlers, E_EVENT_CLIENT_REMOVE, _cb_remove, NULL);
+ return m;
+}
+
+E_API int
+e_modapi_shutdown(E_Module *m EINA_UNUSED)
+{
+ if (_ctl_fd >= 0)
+ {
+ /* _ctl_send may itself close _ctl_fd (and set it to -1) on a hard send
+ * error, so re-check before closing to avoid a spurious close(-1). */
+ _ctl_send(E_WL_PROXY_CTL_BYE, NULL, NULL, 0);
+ if (_ctl_fd >= 0) { close(_ctl_fd); _ctl_fd = -1; }
+ }
+ E_FREE_LIST(_handlers, ecore_event_handler_del);
+ if (_uuid_hook) { e_client_hook_del(_uuid_hook); _uuid_hook = NULL; }
+ if (_ctl_h) { ecore_main_fd_handler_del(_ctl_h); _ctl_h = NULL; }
+ if (_inbuf) { eina_binbuf_free(_inbuf); _inbuf = NULL; }
+ { Restored *r; EINA_LIST_FREE(_restored, r) free(r); }
+ if (_states) { eina_hash_free(_states); _states = NULL; }
+ e_wl_proxy_wm_codec_shutdown();
+ return 1;
+}
+
+E_API int
+e_modapi_save(E_Module *m EINA_UNUSED)
+{
+ return 1;
+}
diff --git a/src/modules/wl_proxy/e_wl_proxy_recv.c b/src/modules/wl_proxy/e_wl_proxy_recv.c
new file mode 100644
index 000000000..d8ae12802
--- /dev/null
+++ b/src/modules/wl_proxy/e_wl_proxy_recv.c
@@ -0,0 +1,42 @@
+#include "config.h"
+#include "e_wl_proxy_recv.h"
+#include "e_wl_proxy_proto.h"
+#include <string.h>
+#include <stdlib.h>
+
+int
+e_wl_proxy_recv_frame(const void *buf, size_t len,
+ char **uuid, E_Wl_Proxy_Wm_State **st, size_t *consumed)
+{
+ E_Wl_Proxy_Ctl_Hdr hdr;
+ size_t total;
+ const char *p = buf;
+
+ *uuid = NULL;
+ *st = NULL;
+
+ if (len < sizeof(hdr)) return 0;
+ memcpy(&hdr, buf, sizeof(hdr));
+ if (hdr.uuid_len > E_WL_PROXY_CTL_MAX_UUID) return -1;
+ if (hdr.blob_len > E_WL_PROXY_CTL_MAX_BLOB) return -1;
+
+ total = sizeof(hdr) + hdr.uuid_len + hdr.blob_len;
+ if (len < total) return 0;
+ *consumed = total;
+
+ if (hdr.type != E_WL_PROXY_CTL_WM_STATE) return 1; /* ignore other types */
+
+ if (hdr.uuid_len)
+ {
+ *uuid = malloc(hdr.uuid_len + 1);
+ if (!*uuid) return 1;
+ memcpy(*uuid, p + sizeof(hdr), hdr.uuid_len);
+ (*uuid)[hdr.uuid_len] = '\0';
+ }
+ if (hdr.blob_len)
+ {
+ *st = e_wl_proxy_wm_decode(p + sizeof(hdr) + hdr.uuid_len, (int)hdr.blob_len);
+ if (!*st) { free(*uuid); *uuid = NULL; }
+ }
+ return 1;
+}
diff --git a/src/modules/wl_proxy/e_wl_proxy_recv.h b/src/modules/wl_proxy/e_wl_proxy_recv.h
new file mode 100644
index 000000000..ca35f0bf5
--- /dev/null
+++ b/src/modules/wl_proxy/e_wl_proxy_recv.h
@@ -0,0 +1,15 @@
+#ifndef E_WL_PROXY_RECV_H
+#define E_WL_PROXY_RECV_H
+
+#include <stddef.h>
+#include "e_wl_proxy_wm.h"
+
+/* Decode one control frame from buf[0..len). On a complete WM_STATE frame:
+ * sets *uuid (NUL-terminated, malloc'd — caller frees), *st (malloc'd decoded
+ * state — caller frees), *consumed to the frame size, returns 1. On a complete
+ * non-WM_STATE frame: sets uuid and st to NULL, *consumed to its size, returns 1.
+ * If more bytes are needed: returns 0. On a malformed frame: returns -1. */
+int e_wl_proxy_recv_frame(const void *buf, size_t len,
+ char **uuid, E_Wl_Proxy_Wm_State **st, size_t *consumed);
+
+#endif
diff --git a/src/modules/wl_proxy/e_wl_proxy_wm.h b/src/modules/wl_proxy/e_wl_proxy_wm.h
new file mode 100644
index 000000000..b5dc94eaf
--- /dev/null
+++ b/src/modules/wl_proxy/e_wl_proxy_wm.h
@@ -0,0 +1,29 @@
+#ifndef E_WL_PROXY_WM_H
+#define E_WL_PROXY_WM_H
+
+/* A flat, self-sufficient snapshot of a window's WM state. Serialized to an
+ * Eet blob the proxy stores opaquely and returns on restore. Adding/removing
+ * a field is safe: Eet's descriptor versioning fills absent fields with 0. */
+typedef struct _E_Wl_Proxy_Wm_State
+{
+ int x, y, w, h; /* frame geometry */
+ int desk_x, desk_y; /* virtual desktop */
+ int zone; /* zone (monitor) index */
+ int layer; /* E_Layer stacking layer */
+ unsigned int maximized; /* E_Maximize direction bitmask */
+ unsigned int stack_seq; /* bumped on raise; higher = nearer top */
+ unsigned int focus_seq; /* bumped on focus; max = focused window */
+ unsigned char fullscreen;
+ unsigned char sticky;
+ unsigned char shaded;
+ unsigned char iconified;
+} E_Wl_Proxy_Wm_State;
+
+void e_wl_proxy_wm_codec_init(void);
+void e_wl_proxy_wm_codec_shutdown(void);
+/* Encode st into a freshly malloc'd blob; *size_out gets its length. Caller frees. */
+void *e_wl_proxy_wm_encode(const E_Wl_Proxy_Wm_State *st, int *size_out);
+/* Decode a blob into a freshly malloc'd state. Caller frees. NULL on error. */
+E_Wl_Proxy_Wm_State *e_wl_proxy_wm_decode(const void *blob, int size);
+
+#endif
diff --git a/src/modules/wl_proxy/e_wl_proxy_wm_codec.c b/src/modules/wl_proxy/e_wl_proxy_wm_codec.c
new file mode 100644
index 000000000..796e201bd
--- /dev/null
+++ b/src/modules/wl_proxy/e_wl_proxy_wm_codec.c
@@ -0,0 +1,48 @@
+#include "config.h"
+#include "e_wl_proxy_wm.h"
+#include <Eet.h>
+#include <Eina.h>
+#include <stdlib.h>
+
+static Eet_Data_Descriptor *_edd = NULL;
+
+void
+e_wl_proxy_wm_codec_init(void)
+{
+ Eet_Data_Descriptor_Class eddc;
+
+ if (_edd) return;
+ EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, E_Wl_Proxy_Wm_State);
+ _edd = eet_data_descriptor_stream_new(&eddc);
+
+#define D(member, type) \
+ EET_DATA_DESCRIPTOR_ADD_BASIC(_edd, E_Wl_Proxy_Wm_State, #member, member, EET_T_##type)
+ D(x, INT); D(y, INT); D(w, INT); D(h, INT);
+ D(desk_x, INT); D(desk_y, INT);
+ D(zone, INT); D(layer, INT);
+ D(maximized, UINT); D(stack_seq, UINT); D(focus_seq, UINT);
+ D(fullscreen, UCHAR); D(sticky, UCHAR); D(shaded, UCHAR); D(iconified, UCHAR);
+#undef D
+}
+
+void
+e_wl_proxy_wm_codec_shutdown(void)
+{
+ if (!_edd) return;
+ eet_data_descriptor_free(_edd);
+ _edd = NULL;
+}
+
+void *
+e_wl_proxy_wm_encode(const E_Wl_Proxy_Wm_State *st, int *size_out)
+{
+ if (!_edd || !st) return NULL;
+ return eet_data_descriptor_encode(_edd, st, size_out);
+}
+
+E_Wl_Proxy_Wm_State *
+e_wl_proxy_wm_decode(const void *blob, int size)
+{
+ if (!_edd || !blob || size <= 0) return NULL;
+ return eet_data_descriptor_decode(_edd, blob, size);
+}
diff --git a/src/modules/wl_proxy/meson.build b/src/modules/wl_proxy/meson.build
new file mode 100644
index 000000000..f83267bd7
--- /dev/null
+++ b/src/modules/wl_proxy/meson.build
@@ -0,0 +1,9 @@
+src = ""
+ 'e_mod_main.c',
+ 'e_wl_proxy_wm_codec.c',
+ 'e_wl_proxy_wm.h',
+ 'e_wl_proxy_recv.c',
+ 'e_wl_proxy_recv.h'
+)
+
+no_icon = true
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.