#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 #include "xdg-shell-client-protocol.h" #include #include #include #include #include #include #include #include #include #include /* ------------------------------------------------------------------ */ /* 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 */