1035 lines
36 KiB
C
1035 lines
36 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 <wayland-client.h>
|
|
#include "xdg-shell-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;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
/* Tell WINGs the backing pointer changed so subsequent
|
|
* draws hit the new buffer. */
|
|
W_SetViewBacking(top->view->window, nb->image);
|
|
top->view->size.width = top->pending_w;
|
|
top->view->size.height = top->pending_h;
|
|
}
|
|
}
|
|
/* 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;
|
|
top->want_close = 1;
|
|
/* TODO: synthesize a WMEvent close so the WINGs app can shut down
|
|
* gracefully. For now we just exit on next event-loop turn. */
|
|
}
|
|
|
|
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)
|
|
{
|
|
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);
|
|
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)
|
|
{
|
|
WLClientToplevel *top = wlc_top_find((WNativeWindow)id);
|
|
if (!top) return;
|
|
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. */
|
|
xdg_toplevel_set_app_id(top->xdg_toplevel, "WINGs");
|
|
xdg_toplevel_set_title(top->xdg_toplevel, "WINGs Application");
|
|
|
|
/* 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);
|
|
}
|
|
|
|
static void
|
|
wl_client_toplevel_destroy(struct W_View *view)
|
|
{
|
|
wl_client_view_destroy_backing((unsigned long)view->window);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 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;
|
|
static WLClientToplevel *wlc_ptr_focused;
|
|
static unsigned int wlc_ptr_button_state;
|
|
static unsigned int wlc_ptr_mods;
|
|
|
|
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;
|
|
|
|
WMEvent ev;
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = WME_ENTER;
|
|
ev.window = wlc_ptr_focused->view->window;
|
|
ev.u.crossing.window = ev.window;
|
|
ev.u.crossing.x = (int)wlc_ptr_x;
|
|
ev.u.crossing.y = (int)wlc_ptr_y;
|
|
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, NULL);
|
|
}
|
|
|
|
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;
|
|
ev.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, NULL);
|
|
wlc_ptr_focused = 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;
|
|
|
|
WMEvent ev;
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = WME_MOTION;
|
|
ev.window = wlc_ptr_focused->view->window;
|
|
ev.u.motion.window = ev.window;
|
|
ev.u.motion.time = time;
|
|
ev.u.motion.x = (int)wlc_ptr_x;
|
|
ev.u.motion.y = (int)wlc_ptr_y;
|
|
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;
|
|
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
|
|
}
|
|
|
|
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; (void)serial;
|
|
WLClientCtx *ctx = data;
|
|
if (!wlc_ptr_focused) return;
|
|
|
|
/* 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;
|
|
ev.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 = (int)wlc_ptr_x;
|
|
ev.u.button.y = (int)wlc_ptr_y;
|
|
ev.u.button.x_root = (int)wlc_ptr_x;
|
|
ev.u.button.y_root = (int)wlc_ptr_y;
|
|
W_WL_EnqueueEvent(ctx->scr, &ev, NULL);
|
|
}
|
|
|
|
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 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static 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);
|
|
}
|
|
}
|
|
|
|
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 */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
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);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
#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 */
|