264 lines
9.1 KiB
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, ®istry_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 */
|