windowmaker-wl/WINGs/wclient_wl.c

264 lines
9.1 KiB
C

/*
* 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 <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#include <xkbcommon/xkbcommon.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;
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;
/* ------------------------------------------------------------------ */
/* 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);
/* Pointer/keyboard probes happen lazily on the first capabilities event;
* we'll add a wl_seat listener below in W_WLClientInit. */
} 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);
}
}
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.
*
* We don't need the prepare/read split when we're only blocking
* here, but doing it correctly keeps us safe if multiple threads
* ever want to share the wl_display.
*/
while (wl_display_prepare_read(ctx->display) != 0) {
if (wl_display_dispatch_pending(ctx->display) < 0) return;
}
if (wl_display_flush(ctx->display) < 0 && errno != EAGAIN) {
wl_display_cancel_read(ctx->display);
return;
}
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) return;
wl_display_dispatch_pending(ctx->display);
}
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
/* Storage for the active context — for now we only support one screen. */
static WLClientCtx *g_wlclient_ctx = NULL;
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);
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);
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);
}
#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 */