windowmaker-wl/WINGs/wclient_wl.c

1466 lines
52 KiB
C

#define WRASTER_NO_XLIB /* Wayland-only file: no libX11 deps */
/*
* wclient_wl.c — Wayland *client* backend for WINGs.
*
* This is the counterpart to wevent_wl.c / wview_wl.c / wfont_wl.c /
* wrender_wl.c, but used when a WINGs program runs as a *client* of an
* external Wayland compositor (e.g. WPrefs.app under wmaker-wl, sway,
* or any wlroots-based compositor).
*
* In compositor mode the WM owns the rendering pipeline and the same
* Wayland backend files run inside the compositor process; surfaces
* are scene-graph nodes managed by wlroots.
*
* In client mode (this file), the program connects via libwayland-client,
* binds the standard globals (wl_compositor, wl_shm, xdg_wm_base, wl_seat),
* creates an xdg_toplevel for each top-level W_View, and submits buffers
* via wl_shm. Pixman backing images are allocated over a memfd-mapped
* region wrapped in a wl_buffer so paints can be done with the same
* pixman calls used everywhere else.
*
* Input dispatch translates wl_pointer / wl_keyboard events into
* WMEvent and feeds them to the existing W_EventBackendWL ring buffer
* via W_WL_EnqueueEvent — the same mechanism the compositor-internal
* Wayland backend uses.
*/
#include "wconfig.h"
#ifdef USE_WAYLAND_CLIENT
#include "WINGsP.h"
#include <wraster.h>
#include "wwindow_p.h"
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#include "wmaker-popup-v1-client-protocol.h"
#include <xkbcommon/xkbcommon.h>
#include <pixman.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
/* ------------------------------------------------------------------ */
/* Per-screen Wayland-client context */
/* ------------------------------------------------------------------ */
typedef struct WLClientCtx {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_shm *shm;
struct wl_seat *seat;
struct xdg_wm_base *wm_base;
/* wmaker-popup-v1: client-driven popup positioning */
struct wmaker_popup_manager_v1 *wmaker_popup_mgr;
/* First non-popup toplevel — used as parent for popup positioning. */
struct xdg_toplevel *main_toplevel;
struct wl_pointer *pointer;
struct wl_keyboard *keyboard;
struct xkb_context *xkb_ctx;
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
/* Back-reference for input handlers */
struct W_Screen *scr;
} WLClientCtx;
/* Storage for the active context — for now we only support one screen. */
static WLClientCtx *g_wlclient_ctx = NULL;
/* Forward decl — defined in wview_wl.c */
extern void W_SetViewBacking(WNativeWindow id, void *img);
/* ------------------------------------------------------------------ */
/* wl_shm-backed buffer allocator */
/* */
/* A WLClientShmBuffer pairs a wl_buffer with the pixman_image_t that */
/* shares the same memfd-mapped region. Painting into the pixman */
/* image touches the same memory the compositor will read on commit, */
/* so no copy is needed. wl_buffer.release marks the buffer as */
/* available for reuse — the surface code may then double-buffer if */
/* it wants, or re-attach the same buffer. */
/* ------------------------------------------------------------------ */
typedef struct WLClientShmBuffer {
struct wl_buffer *wl_buf;
pixman_image_t *image;
void *map;
size_t map_size;
int width;
int height;
int stride; /* bytes per row */
int busy; /* 1 = still owned by compositor */
} WLClientShmBuffer;
static void
shm_buffer_release(void *data, struct wl_buffer *wl_buf)
{
(void)wl_buf;
WLClientShmBuffer *b = data;
if (b) b->busy = 0;
}
static const struct wl_buffer_listener shm_buffer_listener = {
.release = shm_buffer_release,
};
/*
* Create an anonymous, sealed memfd of the given size. We use memfd_create
* with MFD_CLOEXEC because shm_open + unlink is slower and the resulting
* file lingers in /dev/shm. This requires Linux >= 3.17, kernel default
* everywhere we care about.
*/
static int
shm_create_memfd(size_t size)
{
int fd = memfd_create("wings-wl-shm", MFD_CLOEXEC);
if (fd < 0) return -1;
if (ftruncate(fd, size) < 0) {
int saved = errno;
close(fd);
errno = saved;
return -1;
}
return fd;
}
static WLClientShmBuffer *
wlclient_create_shm_buffer(WLClientCtx *ctx, int width, int height)
{
if (!ctx || !ctx->shm || width <= 0 || height <= 0) return NULL;
int stride = width * 4; /* ARGB32, 4 bytes/pixel */
size_t size = (size_t)stride * (size_t)height;
int fd = shm_create_memfd(size);
if (fd < 0) {
wwarning(_("WINGs: memfd_create failed: %s"), strerror(errno));
return NULL;
}
void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
wwarning(_("WINGs: mmap of shm pool failed: %s"), strerror(errno));
close(fd);
return NULL;
}
struct wl_shm_pool *pool = wl_shm_create_pool(ctx->shm, fd, size);
if (!pool) {
munmap(map, size);
close(fd);
return NULL;
}
/* WL_SHM_FORMAT_ARGB8888 matches PIXMAN_a8r8g8b8 byte-for-byte on
* little-endian. WINGs is little-endian-only in practice. */
struct wl_buffer *wl_buf = wl_shm_pool_create_buffer(pool, 0,
width, height, stride, WL_SHM_FORMAT_ARGB8888);
/* Pool can be destroyed immediately after creating the buffer; the
* compositor keeps the fd mapping alive on its side. */
wl_shm_pool_destroy(pool);
/* The fd is duplicated by the compositor; we close ours. */
close(fd);
if (!wl_buf) {
munmap(map, size);
return NULL;
}
WLClientShmBuffer *b = wmalloc(sizeof(*b));
memset(b, 0, sizeof(*b));
b->wl_buf = wl_buf;
b->map = map;
b->map_size = size;
b->width = width;
b->height = height;
b->stride = stride;
b->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height,
(uint32_t *)map, stride);
if (!b->image) {
wl_buffer_destroy(wl_buf);
munmap(map, size);
wfree(b);
return NULL;
}
wl_buffer_add_listener(wl_buf, &shm_buffer_listener, b);
return b;
}
static void
wlclient_destroy_shm_buffer(WLClientShmBuffer *b)
{
if (!b) return;
if (b->image) pixman_image_unref(b->image);
if (b->wl_buf) wl_buffer_destroy(b->wl_buf);
if (b->map && b->map != MAP_FAILED) munmap(b->map, b->map_size);
wfree(b);
}
/* ------------------------------------------------------------------ */
/* Per-toplevel state */
/* */
/* One WLClientToplevel per top-level W_View. Holds the wl_surface, */
/* xdg_surface, xdg_toplevel, and a single shm-backed buffer that */
/* doubles as the view's pixman backing image. */
/* ------------------------------------------------------------------ */
typedef struct WLClientToplevel {
WLClientCtx *ctx;
struct W_View *view;
struct wl_surface *surface;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
/* wmaker-popup-v1 wrapper for client-driven positioning (NULL if not popup) */
struct wmaker_popup_v1 *wmaker_popup;
WLClientShmBuffer *buf; /* current backing */
int pending_w;
int pending_h;
int configured; /* received first xdg_surface.configure */
int mapped; /* surface has at least one buffer */
int want_close;
} WLClientToplevel;
#define WLC_TOPTABLE_SIZE 128
static struct {
WNativeWindow id;
WLClientToplevel *top;
} wlc_toptable[WLC_TOPTABLE_SIZE];
static void
wlc_top_put(WNativeWindow id, WLClientToplevel *top)
{
unsigned idx = ((unsigned)id) % WLC_TOPTABLE_SIZE;
for (int i = 0; i < WLC_TOPTABLE_SIZE; i++) {
unsigned slot = (idx + i) % WLC_TOPTABLE_SIZE;
if (wlc_toptable[slot].id == 0 || wlc_toptable[slot].id == id) {
wlc_toptable[slot].id = id;
wlc_toptable[slot].top = top;
return;
}
}
}
static WLClientToplevel *
wlc_top_find(WNativeWindow id)
{
unsigned idx = ((unsigned)id) % WLC_TOPTABLE_SIZE;
for (int i = 0; i < WLC_TOPTABLE_SIZE; i++) {
unsigned slot = (idx + i) % WLC_TOPTABLE_SIZE;
if (wlc_toptable[slot].id == id) return wlc_toptable[slot].top;
if (wlc_toptable[slot].id == 0) return NULL;
}
return NULL;
}
uint32_t
wlc_top_get_surface_id(WNativeWindow id)
{
WLClientToplevel *top = wlc_top_find(id);
if (!top || !top->surface) return 0;
return wl_proxy_get_id((struct wl_proxy *)top->surface);
}
static void
wlc_top_remove(WNativeWindow id)
{
unsigned idx = ((unsigned)id) % WLC_TOPTABLE_SIZE;
for (int i = 0; i < WLC_TOPTABLE_SIZE; i++) {
unsigned slot = (idx + i) % WLC_TOPTABLE_SIZE;
if (wlc_toptable[slot].id == id) {
wlc_toptable[slot].id = 0;
wlc_toptable[slot].top = NULL;
return;
}
if (wlc_toptable[slot].id == 0) return;
}
}
/* Forward declarations */
static void wl_client_toplevel_commit_locked(WLClientToplevel *top);
/* ------------------------------------------------------------------ */
/* xdg_surface listener */
/* ------------------------------------------------------------------ */
static void
xdg_surface_handle_configure(void *data, struct xdg_surface *xs, uint32_t serial)
{
WLClientToplevel *top = data;
xdg_surface_ack_configure(xs, serial);
top->configured = 1;
wwarning("configure: id=0x%lx pending=(%d,%d) after_configured=%d",
(unsigned long)top->view->window,
top->pending_w, top->pending_h, top->configured);
/* If the compositor configured a new size, resize our backing.
* (Pending size of 0 means "you decide" — keep current.) */
if (top->pending_w > 0 && top->pending_h > 0 &&
(top->pending_w != top->buf->width ||
top->pending_h != top->buf->height)) {
WLClientShmBuffer *nb =
wlclient_create_shm_buffer(top->ctx, top->pending_w, top->pending_h);
if (nb) {
wlclient_destroy_shm_buffer(top->buf);
top->buf = nb;
W_SetViewBacking(top->view->window, nb->image);
top->view->size.width = top->pending_w;
top->view->size.height = top->pending_h;
/* Dispatch configure event synchronously so widget delegates
* relayout children before we repaint. */
WMEvent cev;
memset(&cev, 0, sizeof(cev));
cev.type = WME_CONFIGURE;
cev.window = top->view->window;
cev.u.configure.window = top->view->window;
cev.u.configure.event = top->view->window;
cev.u.configure.width = top->pending_w;
cev.u.configure.height = top->pending_h;
cev._view_target = top->view;
WMHandleEvent(&cev);
/* Repaint all content into the new buffer */
if (top->view->flags.mapped) {
extern void (*W_WLClientRedisplayFn)(W_View *view);
if (W_WLClientRedisplayFn)
W_WLClientRedisplayFn(top->view);
}
}
}
/* On the very first configure, attach the buffer and commit so
* the surface becomes mapped. */
wl_client_toplevel_commit_locked(top);
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
/* ------------------------------------------------------------------ */
/* xdg_toplevel listener */
/* ------------------------------------------------------------------ */
static void
xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xt,
int32_t width, int32_t height,
struct wl_array *states)
{
(void)xt; (void)states;
WLClientToplevel *top = data;
/* Width/height of 0 means the compositor leaves sizing to us. */
top->pending_w = width > 0 ? width : top->buf ? top->buf->width : top->view->size.width;
top->pending_h = height > 0 ? height : top->buf ? top->buf->height : top->view->size.height;
}
static void
xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xt)
{
(void)xt;
WLClientToplevel *top = data;
if (!top || !top->view) return;
W_Screen *scr = top->view->screen;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_CLIENT_MESSAGE;
ev.window = top->view->window;
ev.u.clientMessage.message_type = scr->protocolsAtom;
ev.u.clientMessage.format = 32;
ev.u.clientMessage.data.l[0] = scr->deleteWindowAtom;
W_WL_EnqueueEvent(scr, &ev, top->view);
}
static void
xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *xt,
int32_t width, int32_t height)
{
(void)data; (void)xt; (void)width; (void)height;
}
static void
xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *xt,
struct wl_array *capabilities)
{
(void)data; (void)xt; (void)capabilities;
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_handle_configure,
.close = xdg_toplevel_handle_close,
.configure_bounds = xdg_toplevel_handle_configure_bounds,
.wm_capabilities = xdg_toplevel_handle_wm_capabilities,
};
/* ------------------------------------------------------------------ */
/* Commit / realize / resize / destroy */
/* ------------------------------------------------------------------ */
static void
wl_client_toplevel_commit_locked(WLClientToplevel *top)
{
/* configured==0 means the surface was unmapped via NULL buffer
* and we are waiting for a fresh configure from the compositor.
* All buffer attachments are blocked until it arrives. */
wwarning("commit_locked: id=0x%lx configured=%d buf=%dx%d",
(unsigned long)top->view->window, top->configured,
top->buf ? top->buf->width : 0, top->buf ? top->buf->height : 0);
if (!top || !top->configured || !top->buf || !top->buf->wl_buf)
return;
wl_surface_attach(top->surface, top->buf->wl_buf, 0, 0);
wl_surface_damage_buffer(top->surface, 0, 0,
top->buf->width, top->buf->height);
wl_surface_commit(top->surface);
if (top->ctx && top->ctx->display)
wl_display_flush(top->ctx->display);
top->buf->busy = 1;
top->mapped = 1;
}
static void
wl_client_view_commit(unsigned long id)
{
WLClientToplevel *top = wlc_top_find((WNativeWindow)id);
if (top) wl_client_toplevel_commit_locked(top);
}
static void
wl_client_view_destroy_backing(unsigned long id)
{
extern WLClientToplevel *wlc_kbd_focused;
extern WLClientToplevel *wlc_ptr_focused;
WLClientToplevel *top = wlc_top_find((WNativeWindow)id);
if (!top) return;
/* Clear any input-focus references that point to this toplevel
* before we free it, so subsequent input events don't dereference
* a dangling pointer. */
if (wlc_kbd_focused == top) wlc_kbd_focused = NULL;
if (wlc_ptr_focused == top) wlc_ptr_focused = NULL;
if (top->wmaker_popup) wmaker_popup_v1_destroy(top->wmaker_popup);
if (top->xdg_toplevel) xdg_toplevel_destroy(top->xdg_toplevel);
if (top->xdg_surface) xdg_surface_destroy(top->xdg_surface);
if (top->surface) wl_surface_destroy(top->surface);
if (top->buf) wlclient_destroy_shm_buffer(top->buf);
wlc_top_remove((WNativeWindow)id);
wfree(top);
}
static void *
wl_client_toplevel_realize(struct W_View *view, int width, int height)
{
WLClientCtx *ctx = g_wlclient_ctx;
if (!ctx || !ctx->compositor || !ctx->wm_base) return NULL;
WLClientToplevel *top = wmalloc(sizeof(*top));
memset(top, 0, sizeof(*top));
top->ctx = ctx;
top->view = view;
top->pending_w = width;
top->pending_h = height;
top->buf = wlclient_create_shm_buffer(ctx, width, height);
if (!top->buf) goto fail;
top->surface = wl_compositor_create_surface(ctx->compositor);
if (!top->surface) goto fail;
top->xdg_surface = xdg_wm_base_get_xdg_surface(ctx->wm_base, top->surface);
if (!top->xdg_surface) goto fail;
xdg_surface_add_listener(top->xdg_surface, &xdg_surface_listener, top);
top->xdg_toplevel = xdg_surface_get_toplevel(top->xdg_surface);
if (!top->xdg_toplevel) goto fail;
xdg_toplevel_add_listener(top->xdg_toplevel, &xdg_toplevel_listener, top);
/* Default app_id; the application can override via xdg_toplevel_set_app_id.
* WPrefs and friends ought to call WMSetApplicationIconImage / set_title;
* we'll plumb that next. */
if (view->flags.popup) {
xdg_toplevel_set_app_id(top->xdg_toplevel, "wings-popup");
xdg_toplevel_set_title(top->xdg_toplevel, "");
/* Wrap with wmaker-popup-v1 so we can drive position. */
if (ctx->wmaker_popup_mgr) {
top->wmaker_popup = wmaker_popup_manager_v1_get_popup(
ctx->wmaker_popup_mgr, top->xdg_toplevel);
wwarning("wclient_wl: wrapped popup toplevel id=0x%lx pos=(%d,%d) wrapper=%p",
(unsigned long)view->window, view->pos.x, view->pos.y,
(void*)top->wmaker_popup);
/* Anchor positioning to the application's main toplevel. */
if (top->wmaker_popup && ctx->main_toplevel) {
wmaker_popup_v1_set_parent(top->wmaker_popup,
ctx->main_toplevel);
}
/* Apply any position recorded in view->pos before realize. */
if (top->wmaker_popup && (view->pos.x || view->pos.y)) {
wmaker_popup_v1_set_position(top->wmaker_popup,
view->pos.x, view->pos.y);
}
} else {
wwarning("wclient_wl: popup view but no manager bound");
}
} else {
xdg_toplevel_set_app_id(top->xdg_toplevel, "WINGs");
xdg_toplevel_set_title(top->xdg_toplevel, "WINGs Application");
/* Remember the first regular toplevel as the "main" window so
* subsequent popups can position relative to it. */
if (!ctx->main_toplevel)
ctx->main_toplevel = top->xdg_toplevel;
}
/* Initial commit with no buffer so the compositor sends the first
* configure. We attach a real buffer in the configure handler. */
wl_surface_commit(top->surface);
/* view->window may not be assigned yet at this point — wl_realize sets
* it AFTER allocating the backing. We register the toplevel under
* view->window here using a deferred-association: the caller sets the
* window field first. Actually wview_wl.c assigns view->window BEFORE
* calling W_WLClientToplevelRealizeFn, so we can use it now. */
wlc_top_put((WNativeWindow)view->window, top);
return top->buf->image;
fail:
if (top->xdg_toplevel) xdg_toplevel_destroy(top->xdg_toplevel);
if (top->xdg_surface) xdg_surface_destroy(top->xdg_surface);
if (top->surface) wl_surface_destroy(top->surface);
if (top->buf) wlclient_destroy_shm_buffer(top->buf);
wfree(top);
return NULL;
}
static void
wl_client_toplevel_resize(struct W_View *view, int width, int height)
{
WLClientToplevel *top = wlc_top_find((WNativeWindow)view->window);
if (!top) return;
if (top->buf && top->buf->width == width && top->buf->height == height)
return;
WLClientShmBuffer *nb = wlclient_create_shm_buffer(top->ctx, width, height);
if (!nb) return;
if (top->buf) wlclient_destroy_shm_buffer(top->buf);
top->buf = nb;
W_SetViewBacking(view->window, nb->image);
top->pending_w = width;
top->pending_h = height;
}
static void
wl_client_toplevel_destroy(struct W_View *view)
{
wl_client_view_destroy_backing((unsigned long)view->window);
}
void
wl_client_view_unmap(WNativeWindow id)
{
WLClientToplevel *top = wlc_top_find(id);
if (!top || !top->surface) return;
/* Replace the popup buffer with a 1x1 transparent buffer. This
* hides the popup visually without triggering an xdg unmap, so
* no configure/ack cycle is needed on reopen. The surface stays
* configured and alive. On reopen, resizeMenu creates a new
* correct-size buffer via wl_client_toplevel_resize. */
WLClientShmBuffer *old = top->buf;
top->buf = wlclient_create_shm_buffer(top->ctx, 1, 1);
if (!top->buf) { top->buf = old; return; }
if (old) wlclient_destroy_shm_buffer(old);
W_SetViewBacking(top->view->window, top->buf->image);
/* Shrink the view size to 1x1 so W_ResizeView in resizeMenu detects
* a mismatch on reopen and actually resizes the buffer. Without this,
* W_ResizeView returns early (view already has the correct size from
* the first open) and wl_client_toplevel_resize is never called. */
top->view->size.width = 1;
top->view->size.height = 1;
wl_surface_attach(top->surface, top->buf->wl_buf, 0, 0);
wl_surface_damage_buffer(top->surface, 0, 0, 1, 1);
wl_surface_commit(top->surface);
top->mapped = 0;
if (top->ctx && top->ctx->display)
wl_display_flush(top->ctx->display);
}
void
W_WLClientSetMinMaxSize(W_View *view)
{
WLClientToplevel *top = wlc_top_find((WNativeWindow)view->window);
if (!top || !top->xdg_toplevel) return;
struct W_Window *win = (struct W_Window *)view->self;
if (!win) return;
xdg_toplevel_set_min_size(top->xdg_toplevel,
win->minSize.width, win->minSize.height);
xdg_toplevel_set_max_size(top->xdg_toplevel,
win->maxSize.width, win->maxSize.height);
wl_surface_commit(top->surface);
}
void
W_WLClientPopupSetPosition(WNativeWindow window, int x, int y)
{
WLClientToplevel *top = wlc_top_find(window);
wwarning("wclient_wl: SetPosition id=0x%lx pos=(%d,%d) top=%p wrapper=%p",
(unsigned long)window, x, y, (void*)top,
top ? (void*)top->wmaker_popup : NULL);
if (!top || !top->wmaker_popup) return;
wmaker_popup_v1_set_position(top->wmaker_popup, x, y);
}
struct xkb_keymap *W_WLClientGetKeymap(void)
{
return g_wlclient_ctx ? g_wlclient_ctx->xkb_keymap : NULL;
}
void W_WLClientSetTitle(WNativeWindow window, const char *title)
{
WLClientToplevel *top = wlc_top_find(window);
if (top && top->xdg_toplevel && title)
xdg_toplevel_set_title(top->xdg_toplevel, title);
}
Bool
W_WLClientBeginMove(WNativeWindow window)
{
extern uint32_t wlc_ptr_button_serial;
extern W_View *wlc_ptr_grab;
extern unsigned int wlc_ptr_button_state;
WLClientCtx *ctx = g_wlclient_ctx;
WLClientToplevel *top = wlc_top_find(window);
if (!ctx || !ctx->seat || !top || !top->xdg_toplevel) return False;
if (wlc_ptr_button_serial == 0) return False;
xdg_toplevel_move(top->xdg_toplevel, ctx->seat, wlc_ptr_button_serial);
/* Release implicit grab — the compositor now owns the input until
* the user releases the button. */
wlc_ptr_grab = NULL;
/* Clear pressed state so widgets don't think the button is still down */
wlc_ptr_button_state = 0;
return True;
}
/* ------------------------------------------------------------------ */
void
W_WLClientCommitAllToplevels(void)
{
for (int i = 0; i < WLC_TOPTABLE_SIZE; i++) {
WLClientToplevel *top = wlc_toptable[i].top;
if (top && top->mapped && top->configured && top->buf && top->buf->wl_buf) {
wl_surface_attach(top->surface, top->buf->wl_buf, 0, 0);
wl_surface_damage_buffer(top->surface, 0, 0,
top->buf->width, top->buf->height);
wl_surface_commit(top->surface);
}
}
}
/* ------------------------------------------------------------------ */
/* Input: wl_pointer / wl_keyboard listeners */
/* ------------------------------------------------------------------ */
#include "WMEvent.h"
/* Map an X11-style state mask from xkbcommon modifier state. */
static unsigned int
wlc_xkb_mod_mask(struct xkb_state *state)
{
if (!state) return 0;
unsigned int mask = 0;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= WM_MOD_SHIFT;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= WM_MOD_CONTROL;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= WM_MOD_MOD1;
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO,
XKB_STATE_MODS_EFFECTIVE) > 0)
mask |= WM_MOD_MOD4;
return mask;
}
/* Pointer state */
static double wlc_ptr_x, wlc_ptr_y;
static uint32_t wlc_ptr_focus_serial;
uint32_t wlc_ptr_button_serial; /* serial of most recent button press */
WLClientToplevel *wlc_ptr_focused;
unsigned int wlc_ptr_button_state;
static unsigned int wlc_ptr_mods;
static W_View *wlc_ptr_child_focus;
W_View *wlc_ptr_grab; /* implicit grab: deliver events here while button held */
/*
* Hit-test: find the deepest child view at (x,y) relative to the toplevel,
* and compute local coordinates within that view.
*/
extern W_View *W_FindViewAtPoint(W_View *parent, int x, int y);
static W_View *
wlc_hit_test(WLClientToplevel *top, int x, int y, int *lx, int *ly)
{
if (!top || !top->view) return NULL;
W_View *hit = W_FindViewAtPoint(top->view, x, y);
if (!hit) {
*lx = x;
*ly = y;
return top->view;
}
/* Compute local coords by walking up from hit to toplevel */
int ox = 0, oy = 0;
for (W_View *v = hit; v && v != top->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
}
*lx = x - ox;
*ly = y - oy;
return hit;
}
static WLClientToplevel *
wlc_top_for_surface(struct wl_surface *surf)
{
for (int i = 0; i < WLC_TOPTABLE_SIZE; i++) {
if (wlc_toptable[i].id && wlc_toptable[i].top &&
wlc_toptable[i].top->surface == surf)
return wlc_toptable[i].top;
}
return NULL;
}
static void
pointer_handle_enter(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy)
{
(void)p;
WLClientCtx *ctx = data;
wlc_ptr_focus_serial = serial;
wlc_ptr_focused = wlc_top_for_surface(surface);
wlc_ptr_x = wl_fixed_to_double(sx);
wlc_ptr_y = wl_fixed_to_double(sy);
if (!wlc_ptr_focused) return;
/* Hit-test to find the deepest child view at the enter point,
* consistent with motion handling. This ensures popup child views
* (e.g. menuView) receive proper WME_ENTER with correct local
* coordinates during drag-selection. */
int hit_lx, hit_ly;
W_View *hit = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x,
(int)wlc_ptr_y, &hit_lx, &hit_ly);
wlc_ptr_child_focus = hit;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_ENTER;
ev.window = hit ? hit->window : wlc_ptr_focused->view->window;
ev.u.crossing.window = ev.window;
ev.u.crossing.x = hit_lx;
ev.u.crossing.y = hit_ly;
ev.u.crossing.x_root = (int)wlc_ptr_x;
ev.u.crossing.y_root = (int)wlc_ptr_y;
ev.u.crossing.mode = WM_NOTIFY_NORMAL;
ev.u.crossing.detail = WM_NOTIFY_ANCESTOR;
W_WL_EnqueueEvent(ctx->scr, &ev, hit);
}
static void
pointer_handle_leave(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *surface)
{
(void)p; (void)serial; (void)surface;
WLClientCtx *ctx = data;
if (!wlc_ptr_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_LEAVE;
/* Deliver leave to the deepest child that was under the pointer,
* consistent with the child that received the matching enter. */
ev.window = wlc_ptr_child_focus
? wlc_ptr_child_focus->window
: wlc_ptr_focused->view->window;
ev.u.crossing.window = ev.window;
ev.u.crossing.mode = WM_NOTIFY_NORMAL;
ev.u.crossing.detail = WM_NOTIFY_ANCESTOR;
W_WL_EnqueueEvent(ctx->scr, &ev, wlc_ptr_child_focus);
wlc_ptr_focused = NULL;
wlc_ptr_child_focus = NULL;
}
static void
pointer_handle_motion(void *data, struct wl_pointer *p, uint32_t time,
wl_fixed_t sx, wl_fixed_t sy)
{
(void)p;
WLClientCtx *ctx = data;
wlc_ptr_x = wl_fixed_to_double(sx);
wlc_ptr_y = wl_fixed_to_double(sy);
if (!wlc_ptr_focused) return;
int lx, ly;
W_View *target;
/* During implicit grab (button held), deliver to grabbed view.
* If the pointer has entered a different toplevel surface (e.g. a
* popup menu opened during the drag), hit-test against the focused
* surface instead so motion events reach the correct child view
* (e.g. menuView) with proper local coordinates. */
if (wlc_ptr_grab) {
/* Check if grab target is within the currently focused toplevel */
int grab_in_focused = 0;
for (W_View *v = wlc_ptr_grab; v; v = v->parent) {
if (v == wlc_ptr_focused->view) {
grab_in_focused = 1;
break;
}
}
if (grab_in_focused) {
target = wlc_ptr_grab;
int ox = 0, oy = 0;
for (W_View *v = target; v && v != wlc_ptr_focused->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
}
lx = (int)wlc_ptr_x - ox;
ly = (int)wlc_ptr_y - oy;
} else {
/* Pointer entered a different toplevel — hit-test there */
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
} else {
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
/* Synthesize enter/leave when pointer moves between child views */
if (target != wlc_ptr_child_focus) {
if (wlc_ptr_child_focus) {
WMEvent lev;
memset(&lev, 0, sizeof(lev));
lev.type = WME_LEAVE;
lev.window = wlc_ptr_child_focus->window;
lev.u.crossing.window = lev.window;
lev.u.crossing.mode = WM_NOTIFY_NORMAL;
lev.u.crossing.detail = WM_NOTIFY_ANCESTOR;
W_WL_EnqueueEvent(ctx->scr, &lev, wlc_ptr_child_focus);
}
wlc_ptr_child_focus = target;
if (target) {
WMEvent eev;
memset(&eev, 0, sizeof(eev));
eev.type = WME_ENTER;
eev.window = target->window;
eev.u.crossing.window = eev.window;
eev.u.crossing.x = lx;
eev.u.crossing.y = ly;
eev.u.crossing.mode = WM_NOTIFY_NORMAL;
eev.u.crossing.detail = WM_NOTIFY_ANCESTOR;
W_WL_EnqueueEvent(ctx->scr, &eev, target);
}
}
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_MOTION;
ev.window = target ? target->window : wlc_ptr_focused->view->window;
ev.u.motion.window = ev.window;
ev.u.motion.time = time;
ev.u.motion.x = lx;
ev.u.motion.y = ly;
ev.u.motion.x_root = (int)wlc_ptr_x;
ev.u.motion.y_root = (int)wlc_ptr_y;
ev.u.motion.state = wlc_ptr_button_state | wlc_ptr_mods;
/* Also set button.x/y — some widgets (wscroller) read motion coords
* via event->u.button.x which is at a different union offset. */
ev.u.button.x = lx;
ev.u.button.y = ly;
W_WL_EnqueueEvent(ctx->scr, &ev, target);
}
static void
pointer_handle_button(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state)
{
(void)p;
WLClientCtx *ctx = data;
if (!wlc_ptr_focused) return;
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
wlc_ptr_button_serial = serial;
/* Linux input event codes -> X-style button numbers. */
unsigned int wmb;
switch (button) {
case 0x110 /*BTN_LEFT*/: wmb = 1; break;
case 0x112 /*BTN_MIDDLE*/: wmb = 2; break;
case 0x111 /*BTN_RIGHT*/: wmb = 3; break;
case 0x113 /*BTN_SIDE*/: wmb = 8; break;
case 0x114 /*BTN_EXTRA*/: wmb = 9; break;
default: wmb = 0; break;
}
if (!wmb) return;
unsigned int mask = (wmb == 1) ? WM_BUTTON1_MASK :
(wmb == 2) ? WM_BUTTON2_MASK :
(wmb == 3) ? WM_BUTTON3_MASK : 0;
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
wlc_ptr_button_state |= mask;
else
wlc_ptr_button_state &= ~mask;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = (state == WL_POINTER_BUTTON_STATE_PRESSED)
? WME_BUTTON_PRESS : WME_BUTTON_RELEASE;
int lx, ly;
W_View *target;
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
wlc_ptr_grab = target;
} else {
/* Deliver release to the grabbed view. If the pointer has moved
* to a different toplevel (e.g. popup menu), hit-test there. */
target = wlc_ptr_grab;
if (target) {
int grab_in_focused = 0;
for (W_View *v = target; v; v = v->parent) {
if (v == wlc_ptr_focused->view) {
grab_in_focused = 1;
break;
}
}
if (grab_in_focused) {
int ox = 0, oy = 0;
for (W_View *v = target; v && v != wlc_ptr_focused->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
}
lx = (int)wlc_ptr_x - ox;
ly = (int)wlc_ptr_y - oy;
} else {
/* Release on a different surface — hit-test there */
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
} else {
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
wlc_ptr_grab = NULL;
}
ev.window = target ? target->window : wlc_ptr_focused->view->window;
ev.u.button.window = ev.window;
ev.u.button.button = wmb;
ev.u.button.state = wlc_ptr_button_state | wlc_ptr_mods;
ev.u.button.time = time;
ev.u.button.x = lx;
ev.u.button.y = ly;
ev.u.button.x_root = (int)wlc_ptr_x;
ev.u.button.y_root = (int)wlc_ptr_y;
W_WL_EnqueueEvent(ctx->scr, &ev, target);
}
static void
pointer_handle_axis(void *data, struct wl_pointer *p, uint32_t time,
uint32_t axis, wl_fixed_t value)
{
(void)data; (void)p; (void)time; (void)axis; (void)value;
/* TODO: synthesize button 4/5 events for vertical scroll once we
* verify the basic flow. For now scroll is dropped. */
}
static void pointer_handle_frame(void *d, struct wl_pointer *p) { (void)d; (void)p; }
static void pointer_handle_axis_source(void *d, struct wl_pointer *p, uint32_t s) { (void)d; (void)p; (void)s; }
static void pointer_handle_axis_stop(void *d, struct wl_pointer *p, uint32_t t, uint32_t a) { (void)d; (void)p; (void)t; (void)a; }
static void pointer_handle_axis_discrete(void *d, struct wl_pointer *p, uint32_t a, int32_t v) { (void)d; (void)p; (void)a; (void)v; }
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_handle_enter,
.leave = pointer_handle_leave,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = pointer_handle_axis,
.frame = pointer_handle_frame,
.axis_source = pointer_handle_axis_source,
.axis_stop = pointer_handle_axis_stop,
.axis_discrete = pointer_handle_axis_discrete,
};
/* ------------------------------------------------------------------ */
/* Keyboard */
/* ------------------------------------------------------------------ */
WLClientToplevel *wlc_kbd_focused;
static void
keyboard_handle_keymap(void *data, struct wl_keyboard *k,
uint32_t format, int fd, uint32_t size)
{
(void)k;
WLClientCtx *ctx = data;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
return;
}
char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (map == MAP_FAILED) return;
struct xkb_keymap *km = xkb_keymap_new_from_string(ctx->xkb_ctx, map,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map, size);
if (!km) return;
if (ctx->xkb_state) xkb_state_unref(ctx->xkb_state);
if (ctx->xkb_keymap) xkb_keymap_unref(ctx->xkb_keymap);
ctx->xkb_keymap = km;
ctx->xkb_state = xkb_state_new(km);
}
static void
keyboard_handle_enter(void *data, struct wl_keyboard *k, uint32_t serial,
struct wl_surface *surface, struct wl_array *keys)
{
(void)k; (void)serial; (void)keys;
WLClientCtx *ctx = data;
wlc_kbd_focused = wlc_top_for_surface(surface);
if (!wlc_kbd_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_FOCUS_IN;
ev.window = wlc_kbd_focused->view->window;
ev.u.focus.window = ev.window;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
keyboard_handle_leave(void *data, struct wl_keyboard *k, uint32_t serial,
struct wl_surface *surface)
{
(void)k; (void)serial; (void)surface;
WLClientCtx *ctx = data;
if (!wlc_kbd_focused) return;
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = WME_FOCUS_OUT;
ev.window = wlc_kbd_focused->view->window;
ev.u.focus.window = ev.window;
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
wlc_kbd_focused = NULL;
}
static void
keyboard_handle_key(void *data, struct wl_keyboard *k, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state)
{
(void)k; (void)serial;
WLClientCtx *ctx = data;
if (!wlc_kbd_focused || !ctx->xkb_state) return;
/* wl_keyboard.key gives a Linux evdev keycode; xkbcommon expects +8. */
uint32_t xkb_keycode = key + 8;
xkb_keysym_t sym = xkb_state_key_get_one_sym(ctx->xkb_state, xkb_keycode);
char utf8[8] = {0};
int utf8_len = 0;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
utf8_len = xkb_state_key_get_utf8(ctx->xkb_state, xkb_keycode,
utf8, sizeof(utf8));
if (utf8_len < 0) utf8_len = 0;
if (utf8_len > 7) utf8_len = 7;
}
WMEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = (state == WL_KEYBOARD_KEY_STATE_PRESSED)
? WME_KEY_PRESS : WME_KEY_RELEASE;
ev.window = wlc_kbd_focused->view->window;
ev.u.key.window = ev.window;
ev.u.key.keycode = xkb_keycode;
ev.u.key.state = wlc_ptr_button_state | wlc_ptr_mods;
ev.u.key.time = time;
ev.u.key.keysym = sym;
if (utf8_len > 0) {
memcpy(ev.u.key.utf8, utf8, utf8_len);
ev.u.key.utf8_len = utf8_len;
}
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
}
static void
keyboard_handle_modifiers(void *data, struct wl_keyboard *k, uint32_t serial,
uint32_t mods_depressed, uint32_t mods_latched,
uint32_t mods_locked, uint32_t group)
{
(void)k; (void)serial;
WLClientCtx *ctx = data;
if (ctx->xkb_state) {
xkb_state_update_mask(ctx->xkb_state,
mods_depressed, mods_latched, mods_locked,
0, 0, group);
wlc_ptr_mods = wlc_xkb_mod_mask(ctx->xkb_state);
}
}
static void
keyboard_handle_repeat_info(void *data, struct wl_keyboard *k,
int32_t rate, int32_t delay)
{
(void)data; (void)k; (void)rate; (void)delay;
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_handle_keymap,
.enter = keyboard_handle_enter,
.leave = keyboard_handle_leave,
.key = keyboard_handle_key,
.modifiers = keyboard_handle_modifiers,
.repeat_info = keyboard_handle_repeat_info,
};
/* ------------------------------------------------------------------ */
/* Seat capabilities */
/* ------------------------------------------------------------------ */
static void
seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps)
{
WLClientCtx *ctx = data;
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !ctx->pointer) {
ctx->pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(ctx->pointer, &pointer_listener, ctx);
} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && ctx->pointer) {
wl_pointer_release(ctx->pointer);
ctx->pointer = NULL;
}
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !ctx->keyboard) {
ctx->keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(ctx->keyboard, &keyboard_listener, ctx);
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && ctx->keyboard) {
wl_keyboard_release(ctx->keyboard);
ctx->keyboard = NULL;
}
}
static void
seat_handle_name(void *data, struct wl_seat *seat, const char *name)
{
(void)data; (void)seat; (void)name;
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = seat_handle_name,
};
/* ------------------------------------------------------------------ */
/* xdg_wm_base ping */
/* ------------------------------------------------------------------ */
static void
wm_base_ping(void *data, struct xdg_wm_base *base, uint32_t serial)
{
(void)data;
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener wm_base_listener = {
.ping = wm_base_ping,
};
/* ------------------------------------------------------------------ */
/* Registry — bind required globals */
/* ------------------------------------------------------------------ */
static void
registry_handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
WLClientCtx *ctx = data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
uint32_t v = version < 4 ? version : 4;
ctx->compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, v);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
ctx->shm = wl_registry_bind(registry, name,
&wl_shm_interface, 1);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
uint32_t v = version < 5 ? version : 5;
ctx->seat = wl_registry_bind(registry, name,
&wl_seat_interface, v);
wl_seat_add_listener(ctx->seat, &seat_listener, ctx);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
uint32_t v = version < 3 ? version : 3;
ctx->wm_base = wl_registry_bind(registry, name,
&xdg_wm_base_interface, v);
xdg_wm_base_add_listener(ctx->wm_base, &wm_base_listener, ctx);
} else if (strcmp(interface, wmaker_popup_manager_v1_interface.name) == 0) {
ctx->wmaker_popup_mgr = wl_registry_bind(registry, name,
&wmaker_popup_manager_v1_interface, 1);
wwarning("wclient_wl: bound wmaker_popup_manager_v1");
}
}
static void
registry_handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name)
{
(void)data; (void)registry; (void)name;
/* Globals like seat or output may go away on hotplug; ignore for now. */
}
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
/* ------------------------------------------------------------------ */
/* Event-loop dispatch (used by W_EventBackendWL when queue is empty) */
/* ------------------------------------------------------------------ */
static void
wlclient_dispatch(int timeout_ms, void *userdata)
{
WLClientCtx *ctx = userdata;
if (!ctx || !ctx->display) return;
/* Standard libwayland-client event-pumping idiom:
* 1. Prepare a read.
* 2. Flush outgoing requests.
* 3. Poll the fd.
* 4. read_events / cancel_read.
* 5. Dispatch any queued events. */
while (wl_display_prepare_read(ctx->display) != 0) {
if (wl_display_dispatch_pending(ctx->display) < 0)
goto dead;
}
if (wl_display_flush(ctx->display) < 0 && errno != EAGAIN) {
wl_display_cancel_read(ctx->display);
goto dead;
}
struct pollfd pfd = {
.fd = wl_display_get_fd(ctx->display),
.events = POLLIN,
};
int n = poll(&pfd, 1, timeout_ms);
if (n <= 0) {
wl_display_cancel_read(ctx->display);
return;
}
if (wl_display_read_events(ctx->display) < 0) goto dead;
if (wl_display_dispatch_pending(ctx->display) < 0) goto dead;
return;
dead:
/* Compositor closed the socket — stop spinning. */
wl_display_disconnect(ctx->display);
ctx->display = NULL;
}
/* Pump the client-side Wayland display so pending events (like
* xdg_toplevel.close) are dispatched. Called from the compositor
* main loop. */
void W_WLClientDispatchPending(void)
{
if (!g_wlclient_ctx || !g_wlclient_ctx->display) return;
/* Read any pending data from the socket and dispatch events */
while (wl_display_prepare_read(g_wlclient_ctx->display) != 0)
wl_display_dispatch_pending(g_wlclient_ctx->display);
wl_display_read_events(g_wlclient_ctx->display);
wl_display_dispatch_pending(g_wlclient_ctx->display);
}
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
Bool
W_WLClientInit(struct W_Screen *scrPtr, const char *display)
{
WLClientCtx *ctx = wmalloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
ctx->scr = scrPtr;
ctx->display = wl_display_connect(
(display && display[0] && strcmp(display, "wayland") != 0)
? display : NULL);
if (!ctx->display) {
wwarning(_("WINGs: wl_display_connect() failed"));
goto fail;
}
ctx->registry = wl_display_get_registry(ctx->display);
if (!ctx->registry) goto fail;
wl_registry_add_listener(ctx->registry, &registry_listener, ctx);
/* Two roundtrips: first to receive globals, second to settle any
* follow-up events from those globals (e.g. wl_seat capabilities). */
if (wl_display_roundtrip(ctx->display) < 0) goto fail;
if (wl_display_roundtrip(ctx->display) < 0) goto fail;
if (!ctx->compositor || !ctx->shm || !ctx->wm_base) {
wwarning(_("WINGs: compositor missing required globals "
"(wl_compositor / wl_shm / xdg_wm_base)"));
goto fail;
}
ctx->xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!ctx->xkb_ctx) goto fail;
/* Hook event-loop dispatch into the WINGs Wayland event backend so
* WMNextEvent / waitForEvent will pump the compositor when idle. */
W_WL_SetLoopDispatch(scrPtr,
wl_display_get_fd(ctx->display),
wlclient_dispatch,
ctx);
/* Wire surface lifecycle into wview_wl.c: top-level views will
* acquire wl_surface + xdg_toplevel on realize, attach + commit on
* paint, and tear down on destroy. */
W_WLClientToplevelRealizeFn = wl_client_toplevel_realize;
W_WLClientToplevelResizeFn = wl_client_toplevel_resize;
W_WLClientToplevelDestroyFn = wl_client_toplevel_destroy;
{
extern void (*W_ViewCommitBackingFn)(unsigned long id);
extern void (*W_ViewDestroyBackingFn)(unsigned long id);
W_ViewCommitBackingFn = wl_client_view_commit;
W_ViewDestroyBackingFn = wl_client_view_destroy_backing;
}
g_wlclient_ctx = ctx;
return True;
fail:
if (ctx->xkb_ctx) xkb_context_unref(ctx->xkb_ctx);
if (ctx->wm_base) xdg_wm_base_destroy(ctx->wm_base);
if (ctx->seat) wl_seat_destroy(ctx->seat);
if (ctx->shm) wl_shm_destroy(ctx->shm);
if (ctx->compositor) wl_compositor_destroy(ctx->compositor);
if (ctx->registry) wl_registry_destroy(ctx->registry);
if (ctx->display) wl_display_disconnect(ctx->display);
wfree(ctx);
return False;
}
void
W_WLClientShutdown(struct W_Screen *scrPtr)
{
(void)scrPtr;
WLClientCtx *ctx = g_wlclient_ctx;
if (!ctx) return;
g_wlclient_ctx = NULL;
if (ctx->xkb_state) xkb_state_unref(ctx->xkb_state);
if (ctx->xkb_keymap) xkb_keymap_unref(ctx->xkb_keymap);
if (ctx->xkb_ctx) xkb_context_unref(ctx->xkb_ctx);
if (ctx->pointer) wl_pointer_destroy(ctx->pointer);
if (ctx->keyboard) wl_keyboard_destroy(ctx->keyboard);
if (ctx->wm_base) xdg_wm_base_destroy(ctx->wm_base);
if (ctx->seat) wl_seat_destroy(ctx->seat);
if (ctx->shm) wl_shm_destroy(ctx->shm);
if (ctx->compositor) wl_compositor_destroy(ctx->compositor);
if (ctx->registry) wl_registry_destroy(ctx->registry);
if (ctx->display) wl_display_disconnect(ctx->display);
wfree(ctx);
}
/* ------------------------------------------------------------------ */
/* W_PlatformBackend instance — registered in widgets.c via weak ref. */
/* ------------------------------------------------------------------ */
/*
* wlclient_open — adapter that matches the W_PlatformBackend.open
* signature. Creates a Wayland-mode WMScreen and runs W_WLClientInit
* on it. Returns NULL on failure so WMOpenScreen can try the next
* backend.
*/
static WMScreen *wlclient_open(const char *display)
{
RContext *rc = RCreateContextWayland(1, 1, NULL);
if (!rc) return NULL;
W_SetNextScreenKind(WM_SCREEN_KIND_WAYLAND);
WMScreen *scr = WMCreateScreenWithRContext(NULL, 0, rc);
if (!scr) return NULL;
if (!W_WLClientInit(scr, display)) {
return NULL;
}
scr->aflags.hasAppIcon = 0;
scr->aflags.simpleApplication = 1;
return scr;
}
/*
* wlclient_detect — returns 1 when this backend should handle the
* requested display. The rules favour explicit configuration first,
* then fall back to standard Wayland-vs-X autodetection (matching
* GTK / Qt behaviour).
*/
static int wlclient_detect(const char *display)
{
/* Explicit "wayland" or "wayland:NAME". */
if (display && strncmp(display, "wayland", 7) == 0)
return 1;
/* Explicit X display string overrides everything else. */
if (display && display[0])
return 0;
/* WMAKER_USE_WAYLAND=1 env override. */
const char *force = getenv("WMAKER_USE_WAYLAND");
if (force && force[0] && force[0] != '0')
return 1;
/* Standard autodetect: WAYLAND_DISPLAY set, DISPLAY unset. */
if (getenv("WAYLAND_DISPLAY") && !getenv("DISPLAY"))
return 1;
return 0;
}
const W_PlatformBackend W_PlatformBackendWLClient = {
.name = "wayland-client",
.detect = wlclient_detect,
.open = wlclient_open,
};
#else /* !USE_WAYLAND_CLIENT */
#include "WINGsP.h"
Bool W_WLClientInit(struct W_Screen *scrPtr, const char *display)
{
(void)scrPtr; (void)display;
return False;
}
void W_WLClientShutdown(struct W_Screen *scrPtr) { (void)scrPtr; }
#endif /* USE_WAYLAND_CLIENT */