windowmaker-wl/src/backend/wayland/wl_backend.c

1221 lines
47 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* wl_backend.c -- wlroots-based Wayland compositor backend for Window Maker
*
* Phase 31: complete stub vtable. Every slot compiles and returns a safe
* zero/null/false value. Subsequent phases will replace stubs with real
* wlroots implementations one functional area at a time.
* Phase 32: display_open/close/screen_count implemented with wlroots headless
* backend.
* Phase 34: Wayland compositor socket created (clients can connect);
* wlr_output_layout added; monitors_query returns real head geometry.
* Phase 35: Core Wayland protocol globals advertised (wl_compositor,
* wl_subcompositor, wl_seat, wl_data_device_manager, xdg_wm_base).
* Phase 36: wl_shm and wl_output advertised; XDG toplevel listener wired.
* Phase 37: frame callbacks driven by output frame event; XDG configure
* sent on new toplevel so clients complete their first frame.
* Phase 38: per-toplevel view struct with map/unmap/destroy lifecycle;
* synthetic WNativeWindow IDs; context_save/find/delete implemented.
* Phase 39: handle_toplevel_map wires XDG toplevel map to wManageWindow so
* Wayland surfaces appear in scr->focused_window list.
* Phase 40: handle_toplevel_unmap wires XDG toplevel unmap to wUnmanageWindow.
* Phase 41: prop_get_wm_class and window_fetch_name return XDG app_id/title.
* Phase 42: window_get_client_attrs returns WCLIENT_VIEWABLE + surface
* geometry for mapped XDG toplevels.
* Phase 43: focus_set delivers keyboard focus via wlr_seat; headless keyboard
* device created and attached to seat; mapped toplevels auto-focused.
* Phase 44: client_synth_configure, window_resize send XDG configure events
* so clients receive size changes from the WM.
* Phase 45: pointer_query returns 1 (always on-screen); wWindowFocus/Unfocus
* guarded on frame!=NULL so wSetFocusTo works without a frame window.
* Phase 46: client_close vtable slot (wlr_xdg_toplevel_send_close);
* title_listener updates wwin->wm_instance/wm_class on set_title signal;
* wClientSendProtocol/wClientKill guarded for Wayland.
* Phase 47: prop_get_normal_hints returns XDG toplevel min/max/current size.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/headless.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/box.h>
/* Generated xdg-shell protocol implementation (Wayland glue code) */
#include "protocols/xdg-shell-protocol.c"
#include <wlr/util/log.h>
#include "../../WindowMaker.h"
#include "../../screen.h"
#include "../../window.h"
#include "../backend.h"
#include "wl_backend.h"
/* ------------------------------------------------------------------ */
/* Phase 38: per-toplevel view and context store */
/* ------------------------------------------------------------------ */
/*
* wl_toplevel_view -- one entry per XDG toplevel managed by the compositor.
* Holds the wlroots surface pointer, three lifecycle listeners, and a
* synthetic WNativeWindow ID used as the policy-layer window handle.
*/
struct wl_toplevel_view {
struct wlr_xdg_surface *xdg_surface;
struct wl_listener map_listener;
struct wl_listener unmap_listener;
struct wl_listener destroy_listener;
struct wl_listener title_listener; /* Phase 46: xdg_toplevel set_title */
WNativeWindow id; /* synthetic handle, >= 0x10000000 */
struct wl_list link; /* wl_state.toplevel_list */
};
/*
* Context store: three per-key linked lists keyed by WNativeWindow.
* Backed by a flat list of (win, data) pairs — fine for the small
* number of windows a typical user has open.
*/
struct wl_ctx_entry {
WNativeWindow win;
void *data;
struct wl_list link;
};
/* ------------------------------------------------------------------ */
/* Global compositor state */
/* ------------------------------------------------------------------ */
struct wl_compositor_state {
struct wl_display *display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_output *output; /* headless output (screen 0) */
struct wlr_output_layout *output_layout; /* Phase 34 */
/* Phase 35: core Wayland protocol globals */
struct wlr_compositor *compositor;
struct wlr_seat *seat;
struct wlr_data_device_manager *data_device_mgr;
struct wlr_xdg_shell *xdg_shell;
/* Phase 36: XDG toplevel tracking */
struct wl_listener xdg_new_surface_listener;
int managed_surface_count; /* surfaces seen */
/* Phase 37: periodic timer that drives client frame callbacks */
struct wl_event_source *frame_timer;
/* Phase 38: per-toplevel view list and context store */
struct wl_list toplevel_list; /* wl_toplevel_view.link */
struct wl_list ctx_lists[3]; /* one per WContextKey */
unsigned long next_synthetic_id; /* >= 0x10000000 */
/* Phase 43: headless keyboard device for seat keyboard focus */
struct wlr_input_device *keyboard_dev;
char socket_name[64]; /* Phase 34: e.g. "wayland-0" */
int started; /* non-zero after successful display_open */
};
static struct wl_compositor_state wl_state;
/* ------------------------------------------------------------------ */
/* Phase 36 / 37 / 38: XDG surface lifecycle, frame loop, view mgmt */
/* ------------------------------------------------------------------ */
/*
* Phase 37: Wayland event loop timer — drives client frame callbacks.
*/
static int
frame_timer_cb(void *data)
{
(void)data;
if (wl_state.output)
wlr_output_send_frame(wl_state.output);
if (wl_state.frame_timer)
wl_event_source_timer_update(wl_state.frame_timer, 16);
return 0;
}
/* Helper: recover the view from one of its embedded listeners. */
#define VIEW_FROM_LISTENER(ptr, field) \
((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field)))
/* Phase 43: forward declaration — defined in the focus section below. */
static void wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp);
/* Phase 41: look up the view for a given synthetic WNativeWindow ID. */
static struct wl_toplevel_view *
wl_find_view_by_id(WNativeWindow id)
{
struct wl_toplevel_view *v;
wl_list_for_each(v, &wl_state.toplevel_list, link) {
if (v->id == id)
return v;
}
return NULL;
}
/*
* Phase 46: XDG toplevel title changed — update wwin->wm_instance.
*/
static void
handle_toplevel_title(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, title_listener);
(void)data;
const char *new_title = view->xdg_surface->toplevel->title;
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (!desc || desc->parent_type != WCLASS_WINDOW)
return;
WWindow *wwin = (WWindow *)desc->parent;
if (wwin->wm_instance)
wfree(wwin->wm_instance);
wwin->wm_instance = new_title ? strdup(new_title) : NULL;
if (wwin->wm_class)
wfree(wwin->wm_class);
const char *app_id = (view->xdg_surface->toplevel->app_id)
? view->xdg_surface->toplevel->app_id : "wayland-client";
wwin->wm_class = strdup(app_id);
wWindowUpdateName(wwin, new_title);
}
/*
* Phase 38: toplevel map — surface is ready to be managed.
*/
static void
handle_toplevel_map(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener);
(void)data;
wwarning("wl_backend: toplevel mapped id=0x%lx app_id=%s title=%s",
view->id,
view->xdg_surface->toplevel->app_id ? view->xdg_surface->toplevel->app_id : "(none)",
view->xdg_surface->toplevel->title ? view->xdg_surface->toplevel->title : "(none)");
/* Phase 39/41: register the surface as a managed window. */
{
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WWindow *wwin = wManageWindow(scr, view->id);
if (wwin) {
wwarning("wl_backend: wManageWindow succeeded id=0x%lx "
"class=%s name=%s geo=%dx%d",
view->id,
wwin->wm_class ? wwin->wm_class : "(none)",
wwin->wm_instance ? wwin->wm_instance : "(none)",
wwin->client.width, wwin->client.height);
/* Phase 43: focus the newly mapped toplevel. */
wl_focus_set(view->id, 0, 0);
}
}
}
}
/*
* Phase 40: toplevel unmap — surface hidden or destroyed; unmanage the window.
*/
static void
handle_toplevel_unmap(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, unmap_listener);
(void)data;
wwarning("wl_backend: toplevel unmapped id=0x%lx", view->id);
/* Phase 40: remove the WWindow from the WM's managed list. */
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (desc && desc->parent_type == WCLASS_WINDOW) {
WWindow *wwin = (WWindow *)desc->parent;
wUnmanageWindow(wwin, False, True);
}
}
}
/*
* Phase 38: toplevel destroy — free the view.
*/
static void
handle_toplevel_destroy(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, destroy_listener);
(void)data;
wl_list_remove(&view->map_listener.link);
wl_list_remove(&view->unmap_listener.link);
wl_list_remove(&view->destroy_listener.link);
wl_list_remove(&view->title_listener.link);
wl_list_remove(&view->link);
wl_state.managed_surface_count--;
wfree(view);
}
/*
* Phase 36 / 38: new XDG surface listener.
* Allocates a wl_toplevel_view, assigns a synthetic WNativeWindow ID,
* wires the three lifecycle listeners, and schedules the initial configure.
*/
static void
handle_xdg_new_surface(struct wl_listener *listener, void *data)
{
struct wlr_xdg_surface *xdg_surface = data;
struct wl_toplevel_view *view;
(void)listener;
if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL)
return; /* ignore popups for now */
view = wmalloc(sizeof(*view));
memset(view, 0, sizeof(*view));
view->xdg_surface = xdg_surface;
view->id = wl_state.next_synthetic_id++;
view->map_listener.notify = handle_toplevel_map;
view->unmap_listener.notify = handle_toplevel_unmap;
view->destroy_listener.notify = handle_toplevel_destroy;
view->title_listener.notify = handle_toplevel_title;
wl_signal_add(&xdg_surface->events.map, &view->map_listener);
wl_signal_add(&xdg_surface->events.unmap, &view->unmap_listener);
wl_signal_add(&xdg_surface->events.destroy, &view->destroy_listener);
wl_signal_add(&xdg_surface->toplevel->events.set_title, &view->title_listener);
wl_list_insert(&wl_state.toplevel_list, &view->link);
wl_state.managed_surface_count++;
wwarning("wl_backend: new XDG toplevel id=0x%lx app_id=%s title=%s",
view->id,
xdg_surface->toplevel->app_id ? xdg_surface->toplevel->app_id : "(none)",
xdg_surface->toplevel->title ? xdg_surface->toplevel->title : "(none)");
/* Phase 37: initial configure so the client can commit its first buffer. */
wlr_xdg_surface_schedule_configure(xdg_surface);
}
/* ------------------------------------------------------------------ */
/* Display lifecycle (Phase 32 / Phase 34) */
/* ------------------------------------------------------------------ */
static void *
wl_display_open(const char *name)
{
const char *sock;
(void)name; /* Wayland compositor picks its own socket name */
wlr_log_init(WLR_ERROR, NULL);
wl_state.display = wl_display_create();
if (!wl_state.display) {
wwarning("wl_backend: failed to create wl_display");
return NULL;
}
wl_state.backend = wlr_headless_backend_create(wl_state.display);
if (!wl_state.backend) {
wwarning("wl_backend: failed to create wlroots headless backend");
wl_display_destroy(wl_state.display);
wl_state.display = NULL;
return NULL;
}
wl_state.renderer = wlr_renderer_autocreate(wl_state.backend);
if (!wl_state.renderer) {
wwarning("wl_backend: failed to create wlroots renderer");
wlr_backend_destroy(wl_state.backend);
wl_display_destroy(wl_state.display);
wl_state.backend = NULL;
wl_state.display = NULL;
return NULL;
}
if (!wlr_backend_start(wl_state.backend)) {
wwarning("wl_backend: failed to start wlroots headless backend");
wlr_renderer_destroy(wl_state.renderer);
wlr_backend_destroy(wl_state.backend);
wl_display_destroy(wl_state.display);
wl_state.renderer = NULL;
wl_state.backend = NULL;
wl_state.display = NULL;
return NULL;
}
/* Phase 34: create the UNIX socket so Wayland clients can connect. */
sock = wl_display_add_socket_auto(wl_state.display);
if (!sock) {
wwarning("wl_backend: failed to create Wayland compositor socket");
wlr_renderer_destroy(wl_state.renderer);
wlr_backend_destroy(wl_state.backend);
wl_display_destroy(wl_state.display);
wl_state.renderer = NULL;
wl_state.backend = NULL;
wl_state.display = NULL;
return NULL;
}
snprintf(wl_state.socket_name, sizeof(wl_state.socket_name), "%s", sock);
setenv("WAYLAND_DISPLAY", wl_state.socket_name, 1);
wwarning("wl_backend: compositor socket WAYLAND_DISPLAY=%s", wl_state.socket_name);
/* Phase 34: output layout for multi-head geometry tracking. */
wl_state.output_layout = wlr_output_layout_create();
/* Add a single 1920×1080 headless output (screen 0). */
wl_state.output = wlr_headless_add_output(wl_state.backend, 1920, 1080);
if (wl_state.output && wl_state.output_layout)
wlr_output_layout_add_auto(wl_state.output_layout, wl_state.output);
/* Phase 35: advertise core Wayland protocol globals. */
wl_state.compositor =
wlr_compositor_create(wl_state.display, wl_state.renderer);
wl_state.seat = wlr_seat_create(wl_state.display, "seat0");
wl_state.data_device_mgr =
wlr_data_device_manager_create(wl_state.display);
wl_state.xdg_shell = wlr_xdg_shell_create(wl_state.display);
/* Phase 36: advertise wl_shm (needed by SHM buffer clients) and
* expose the headless output as a wl_output global. */
wlr_renderer_init_wl_shm(wl_state.renderer, wl_state.display);
if (wl_state.output)
wlr_output_create_global(wl_state.output);
/* Phase 43: create a headless keyboard and attach it to the seat so
* wlr_seat_keyboard_notify_enter() can deliver keyboard focus. */
wl_state.keyboard_dev =
wlr_headless_add_input_device(wl_state.backend, WLR_INPUT_DEVICE_KEYBOARD);
if (wl_state.keyboard_dev) {
wlr_seat_set_keyboard(wl_state.seat, wl_state.keyboard_dev);
wlr_seat_set_capabilities(wl_state.seat, WL_SEAT_CAPABILITY_KEYBOARD);
}
/* Phase 38: initialise per-toplevel tracking and context store. */
wl_list_init(&wl_state.toplevel_list);
{
int ki;
for (ki = 0; ki < 3; ki++)
wl_list_init(&wl_state.ctx_lists[ki]);
}
wl_state.next_synthetic_id = 0x10000000UL;
wl_state.managed_surface_count = 0;
/* Phase 36: listen for new XDG toplevel surfaces. */
wl_state.xdg_new_surface_listener.notify = handle_xdg_new_surface;
wl_signal_add(&wl_state.xdg_shell->events.new_surface,
&wl_state.xdg_new_surface_listener);
/* Phase 37: periodic timer to drive client frame callbacks at ~60 fps.
* Using a timer avoids the swapchain/allocator complexity of the full
* render pipeline while still keeping Wayland clients unblocked. */
{
struct wl_event_loop *loop =
wl_display_get_event_loop(wl_state.display);
wl_state.frame_timer =
wl_event_loop_add_timer(loop, frame_timer_cb, NULL);
if (wl_state.frame_timer)
wl_event_source_timer_update(wl_state.frame_timer, 16);
}
wl_state.started = 1;
return NULL; /* no X11 Display* in the Wayland path */
}
static void
wl_display_close(void)
{
if (!wl_state.started)
return;
if (wl_state.frame_timer) {
wl_event_source_remove(wl_state.frame_timer);
wl_state.frame_timer = NULL;
}
if (wl_state.output_layout) {
wlr_output_layout_destroy(wl_state.output_layout);
wl_state.output_layout = NULL;
}
if (wl_state.renderer)
wlr_renderer_destroy(wl_state.renderer);
if (wl_state.backend)
wlr_backend_destroy(wl_state.backend);
if (wl_state.display)
wl_display_destroy(wl_state.display);
unsetenv("WAYLAND_DISPLAY");
wl_state.renderer = NULL;
wl_state.backend = NULL;
wl_state.display = NULL;
wl_state.output = NULL;
wl_state.started = 0;
}
static int
wl_display_screen_count(void)
{
return wl_state.started ? 1 : 0;
}
static int
wl_display_default_screen(void)
{
return 0;
}
static unsigned long
wl_atom_intern(const char *name)
{
(void)name;
return 0; /* atoms have no meaning in native Wayland */
}
static void
wl_event_loop_run(void)
{
if (wl_state.display)
wl_display_run(wl_state.display);
}
/* ------------------------------------------------------------------ */
/* Screen open/close */
/* ------------------------------------------------------------------ */
static void
wl_screen_open(WScreen *scr)
{
/* Set geometry from the headless output created in display_open(). */
scr->scr_width = wl_state.output ?
(int)wl_state.output->width : 1920;
scr->scr_height = wl_state.output ?
(int)wl_state.output->height : 1080;
scr->depth = 24;
scr->root_win = WNATIVE_NONE;
scr->colormap = 0;
}
static void wl_screen_close(WScreen *scr) { (void)scr; }
/* ------------------------------------------------------------------ */
/* Frame / window lifecycle */
/* ------------------------------------------------------------------ */
static WNativeWindow
wl_frame_create_toplevel(WScreen *scr,
int x, int y, int width, int height,
int border_width,
int depth, void *visual,
unsigned long colormap,
WMPixel border_pixel)
{
(void)scr; (void)x; (void)y; (void)width; (void)height;
(void)border_width; (void)depth; (void)visual;
(void)colormap; (void)border_pixel;
return WNATIVE_NONE;
}
static WNativeWindow
wl_frame_create_child(WNativeWindow parent_win, WScreen *scr,
int x, int y, int width, int height)
{
(void)parent_win; (void)scr;
(void)x; (void)y; (void)width; (void)height;
return WNATIVE_NONE;
}
static void wl_frame_destroy(WScreen *scr, WNativeWindow win) { (void)scr; (void)win; }
static void
wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
{ (void)win; (void)x; (void)y; (void)width; (void)height; }
/* ------------------------------------------------------------------ */
/* Context (window → object lookup table) — Phase 38 */
/* ------------------------------------------------------------------ */
static void
wl_context_save(WNativeWindow win, WContextKey key, void *data)
{
struct wl_list *list = &wl_state.ctx_lists[(int)key];
struct wl_ctx_entry *e;
/* update existing entry if present */
wl_list_for_each(e, list, link) {
if (e->win == win) { e->data = data; return; }
}
/* new entry */
e = wmalloc(sizeof(*e));
e->win = win;
e->data = data;
wl_list_insert(list, &e->link);
}
static void *
wl_context_find(WNativeWindow win, WContextKey key)
{
struct wl_list *list = &wl_state.ctx_lists[(int)key];
struct wl_ctx_entry *e;
wl_list_for_each(e, list, link) {
if (e->win == win) return e->data;
}
return NULL;
}
static int
wl_context_delete(WNativeWindow win, WContextKey key)
{
struct wl_list *list = &wl_state.ctx_lists[(int)key];
struct wl_ctx_entry *e, *tmp;
wl_list_for_each_safe(e, tmp, list, link) {
if (e->win == win) {
wl_list_remove(&e->link);
wfree(e);
return 1;
}
}
return 0; /* not found */
}
/* ------------------------------------------------------------------ */
/* Focus */
/* ------------------------------------------------------------------ */
static void
wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp)
{
/* Phase 43: deliver keyboard focus to the XDG toplevel surface. */
(void)revert_to; (void)timestamp;
if (!wl_state.seat || !wl_state.keyboard_dev)
return;
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || !v->xdg_surface->surface)
return;
wlr_seat_keyboard_notify_enter(wl_state.seat,
v->xdg_surface->surface,
NULL, 0, NULL);
wwarning("wl_backend: focus_set id=0x%lx", win);
}
/* ------------------------------------------------------------------ */
/* Key / button grabs */
/* ------------------------------------------------------------------ */
static void
wl_key_grab(WNativeWindow win, unsigned int keycode,
unsigned int modifier, int owner_events)
{ (void)win; (void)keycode; (void)modifier; (void)owner_events; }
static void wl_key_ungrab_all(WNativeWindow win) { (void)win; }
static void
wl_button_grab(WNativeWindow win, unsigned int button,
unsigned int modifier, unsigned int event_mask,
int owner_events)
{ (void)win; (void)button; (void)modifier; (void)event_mask; (void)owner_events; }
static void wl_button_ungrab_all(WNativeWindow win) { (void)win; }
/* ------------------------------------------------------------------ */
/* Pointer grabs */
/* ------------------------------------------------------------------ */
static int
wl_pointer_grab(WNativeWindow grab_win, WNativeWindow confine_to,
unsigned int event_mask, int cursor_id,
int owner_events, unsigned long timestamp)
{
(void)grab_win; (void)confine_to; (void)event_mask;
(void)cursor_id; (void)owner_events; (void)timestamp;
return 0;
}
static void wl_pointer_ungrab(unsigned long timestamp) { (void)timestamp; }
static void wl_pointer_warp(int dx, int dy) { (void)dx; (void)dy; }
static void wl_pointer_warp_to(int x, int y) { (void)x; (void)y; }
static int
wl_pointer_query(WNativeWindow root, int *root_x, int *root_y)
{
/* Phase 45: the Wayland compositor IS the screen; always on-screen. */
(void)root;
*root_x = *root_y = 0;
return wl_state.started ? 1 : 0;
}
static int
wl_pointer_query_full(WNativeWindow root,
WNativeWindow *child_out,
int *root_x, int *root_y,
unsigned int *buttons_out)
{
/* Phase 45: same — always on-screen when compositor is running. */
(void)root;
if (child_out) *child_out = WNATIVE_NONE;
*root_x = *root_y = 0;
if (buttons_out) *buttons_out = 0;
return wl_state.started ? 1 : 0;
}
static void
wl_pointer_change_grab(unsigned int event_mask, int cursor_id,
unsigned long timestamp)
{ (void)event_mask; (void)cursor_id; (void)timestamp; }
/* ------------------------------------------------------------------ */
/* Keyboard grabs */
/* ------------------------------------------------------------------ */
static int wl_keyboard_grab(WNativeWindow win) { (void)win; return 0; }
static void wl_keyboard_ungrab(void) {}
/* ------------------------------------------------------------------ */
/* Server grab */
/* ------------------------------------------------------------------ */
static void wl_server_grab(void) {}
static void wl_server_ungrab(void) {}
/* ------------------------------------------------------------------ */
/* Stacking */
/* ------------------------------------------------------------------ */
static void wl_stacking_raise(WNativeWindow win) { (void)win; }
static void wl_stacking_lower(WNativeWindow win) { (void)win; }
static void
wl_stacking_restack(WNativeWindow *windows, int count)
{ (void)windows; (void)count; }
static int
wl_stacking_query(WNativeWindow root,
WNativeWindow **out, unsigned int *count)
{
(void)root;
*out = NULL; *count = 0;
return 0;
}
/* ------------------------------------------------------------------ */
/* Events */
/* ------------------------------------------------------------------ */
static void wl_event_flush(void) {}
static void wl_event_flush_expose(void) {}
static void wl_event_sync(int discard) { (void)discard; }
static int wl_event_pending(void) { return 0; }
static void wl_event_drain_motion(void) {}
static void wl_event_discard_enter_leave_until(unsigned long timestamp) { (void)timestamp; }
static void wl_event_handle_extensions(void *event) { (void)event; }
static void wl_event_randr_update(void *event) { (void)event; }
/* ------------------------------------------------------------------ */
/* Properties */
/* ------------------------------------------------------------------ */
static int
wl_prop_get_normal_hints(WNativeWindow win, WNormalHints *out, int *pre_icccm)
{
/* Phase 47: populate size constraints from XDG toplevel. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
memset(out, 0, sizeof(*out));
*pre_icccm = 0;
if (!v || !v->xdg_surface || !v->xdg_surface->toplevel)
return 0;
struct wlr_xdg_toplevel *tl = v->xdg_surface->toplevel;
if (tl->current.min_width > 0 || tl->current.min_height > 0) {
out->flags |= WNH_P_MIN_SIZE;
out->min_width = (int)tl->current.min_width;
out->min_height = (int)tl->current.min_height;
}
if (tl->current.max_width > 0 || tl->current.max_height > 0) {
out->flags |= WNH_P_MAX_SIZE;
out->max_width = (int)tl->current.max_width;
out->max_height = (int)tl->current.max_height;
}
/* Report the current size as the base/initial size. */
if (v->xdg_surface->surface) {
int w = v->xdg_surface->surface->current.width;
int h = v->xdg_surface->surface->current.height;
if (w > 0 && h > 0) {
out->flags |= WNH_P_SIZE;
out->width = w;
out->height = h;
}
}
return (out->flags != 0) ? 1 : 0;
}
static WWMHints *
wl_prop_get_wm_hints(WNativeWindow win) { (void)win; return NULL; }
static int
wl_prop_get_wm_class(WNativeWindow win,
char **wm_class, char **wm_instance)
{
/* Phase 41: use XDG app_id as both WM_CLASS and WM_INSTANCE. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
const char *app_id = (v && v->xdg_surface && v->xdg_surface->toplevel &&
v->xdg_surface->toplevel->app_id)
? v->xdg_surface->toplevel->app_id : "wayland-client";
*wm_class = strdup(app_id);
*wm_instance = strdup(app_id);
return 0;
}
static Bool
wl_window_fetch_name(WNativeWindow win, char **name_out)
{
/* Phase 41: use XDG toplevel title as the window name. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
const char *title = (v && v->xdg_surface && v->xdg_surface->toplevel &&
v->xdg_surface->toplevel->title)
? v->xdg_surface->toplevel->title : NULL;
if (title) {
*name_out = strdup(title);
return True;
}
*name_out = NULL;
return False;
}
static Bool
wl_window_get_icon_name(WNativeWindow win, char **name_out)
{ (void)win; *name_out = NULL; return False; }
static int
wl_prop_get_wmaker_menu(WNativeWindow win,
char ***list_out, int *count_out)
{
(void)win;
*list_out = NULL; *count_out = 0;
return 0;
}
static WNativeWindow
wl_prop_get_transient_for(WNativeWindow win) { (void)win; return WNATIVE_NONE; }
static int wl_prop_has_wmaker_menu(WNativeWindow win) { (void)win; return 0; }
static int wl_prop_has_gtk_object_path(WNativeWindow win) { (void)win; return 0; }
static void
wl_prop_set_wm_state(WNativeWindow win, const long *data, int n)
{ (void)win; (void)data; (void)n; }
static int
wl_prop_get_wm_state(WNativeWindow win, long **data_out)
{ (void)win; *data_out = NULL; return 0; }
/* ------------------------------------------------------------------ */
/* Window map / unmap / geometry */
/* ------------------------------------------------------------------ */
static void wl_window_map(WNativeWindow win) { (void)win; }
static void wl_window_map_raised(WNativeWindow win) { (void)win; }
static void wl_window_map_subwindows(WNativeWindow parent) { (void)parent; }
static void wl_window_unmap(WNativeWindow win) { (void)win; }
static void wl_window_move(WNativeWindow win, int x, int y)
{
/* Phase 44: position is tracked on the WM side; no server-side move needed. */
(void)win; (void)x; (void)y;
}
static void wl_window_resize(WNativeWindow win, int w, int h)
{
/* Phase 44: send a configure with the new size. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || w <= 0 || h <= 0)
return;
wlr_xdg_toplevel_set_size(v->xdg_surface, (uint32_t)w, (uint32_t)h);
wlr_xdg_surface_schedule_configure(v->xdg_surface);
}
static void wl_window_set_border(WNativeWindow win, unsigned long pixel) { (void)win; (void)pixel; }
static void wl_window_set_border_width(WNativeWindow win, int width) { (void)win; (void)width; }
static void wl_window_set_save_under(WNativeWindow win, int save_under) { (void)win; (void)save_under; }
static void wl_window_clear(WNativeWindow win) { (void)win; }
static int
wl_window_get_rect(WNativeWindow win,
int *win_x, int *win_y, int *win_w, int *win_h)
{
(void)win;
*win_x = *win_y = 0; *win_w = *win_h = 1;
return 0;
}
static void wl_window_add_events(WNativeWindow win, unsigned long extra_mask) { (void)win; (void)extra_mask; }
static void wl_window_select_events(WNativeWindow win, unsigned long mask) { (void)win; (void)mask; }
static void wl_window_shape_clear(WWindow *wwin) { (void)wwin; }
static void wl_window_shape_set(WWindow *wwin) { (void)wwin; }
/* ------------------------------------------------------------------ */
/* Overlays */
/* ------------------------------------------------------------------ */
static void wl_overlay_draw_line(WScreen *scr, int x1, int y1, int x2, int y2) { (void)scr; (void)x1; (void)y1; (void)x2; (void)y2; }
static void wl_overlay_draw_lines(WScreen *scr, WPoint *pts, int n) { (void)scr; (void)pts; (void)n; }
static void wl_overlay_draw_rect(WScreen *scr, int x, int y, int w, int h) { (void)scr; (void)x; (void)y; (void)w; (void)h; }
static void wl_overlay_draw_segments(WScreen *scr, WSegment *segs, int n) { (void)scr; (void)segs; (void)n; }
static void wl_overlay_draw_string(WScreen *scr, int x, int y, const char *str, int len) { (void)scr; (void)x; (void)y; (void)str; (void)len; }
static int wl_overlay_text_width(WScreen *scr, const char *str, int n) { (void)scr; (void)str; (void)n; return 0; }
/* ------------------------------------------------------------------ */
/* Client */
/* ------------------------------------------------------------------ */
static int
wl_window_get_client_attrs(WNativeWindow win, WClientAttributes *out)
{
/* Phase 42: return real map_state and geometry from XDG surface. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
memset(out, 0, sizeof(*out));
if (v && v->xdg_surface && v->xdg_surface->surface) {
out->map_state = WCLIENT_VIEWABLE;
out->width = v->xdg_surface->surface->current.width;
out->height = v->xdg_surface->surface->current.height;
} else {
out->map_state = WCLIENT_UNMAPPED;
}
return 1;
}
static void
wl_client_get_visual_attrs(WNativeWindow win, WClientVisual *out)
{
(void)win;
out->depth = 24; out->visual = NULL; out->colormap = 0;
}
static void
wl_client_adopt_attributes(WNativeWindow win, unsigned long event_mask)
{ (void)win; (void)event_mask; }
static void
wl_client_reparent(WNativeWindow client, WNativeWindow new_parent, int x, int y)
{ (void)client; (void)new_parent; (void)x; (void)y; }
static void
wl_client_synth_configure(WNativeWindow client_win,
int x, int y, int width, int height,
int border_width, WNativeWindow above)
{
/* Phase 44: send an XDG configure with the new size to the client. */
(void)x; (void)y; (void)border_width; (void)above;
struct wl_toplevel_view *v = wl_find_view_by_id(client_win);
if (!v || !v->xdg_surface)
return;
if (width > 0 && height > 0)
wlr_xdg_toplevel_set_size(v->xdg_surface, (uint32_t)width, (uint32_t)height);
wlr_xdg_surface_schedule_configure(v->xdg_surface);
}
static void wl_client_add_to_save_set(WNativeWindow win) { (void)win; }
static void wl_client_remove_from_save_set(WNativeWindow win) { (void)win; }
static void
wl_client_close(WNativeWindow win)
{
/* Phase 46: ask the XDG toplevel to close itself. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->xdg_surface)
wlr_xdg_toplevel_send_close(v->xdg_surface);
}
static void wl_client_query_shaped(WNativeWindow win, int *shaped_out)
{ (void)win; *shaped_out = 0; }
static int
wl_client_get_gnustep_attrs(WNativeWindow win, GNUstepWMAttributes **attr_out)
{ (void)win; *attr_out = NULL; return 0; }
static int wl_client_get_pid(WNativeWindow win) { (void)win; return 0; }
static RImage *
wl_client_snapshot(WScreen *scr, WNativeWindow client_win, WNativeWindow frame_win)
{ (void)scr; (void)client_win; (void)frame_win; return NULL; }
static int
wl_coords_translate(WNativeWindow src, WNativeWindow dst,
int src_x, int src_y, int *dst_x, int *dst_y)
{
(void)src; (void)dst;
*dst_x = src_x; *dst_y = src_y;
return 0;
}
/* ------------------------------------------------------------------ */
/* ICCCM */
/* ------------------------------------------------------------------ */
static void wl_icccm_set_wm_hints(WNativeWindow win, const WWMHints *hints) { (void)win; (void)hints; }
static void wl_icccm_set_class_hint(WNativeWindow win, const char *wm_class, const char *wm_instance) { (void)win; (void)wm_class; (void)wm_instance; }
static void wl_icccm_set_class_hint_if_absent(WNativeWindow win, const char *wm_class, const char *wm_instance) { (void)win; (void)wm_class; (void)wm_instance; }
static void wl_icccm_set_command(WNativeWindow win, char **argv, int argc) { (void)win; (void)argv; (void)argc; }
static int wl_icccm_get_command(WNativeWindow win, char ***argv_out, int *argc_out) { (void)win; *argv_out = NULL; *argc_out = 0; return 0; }
static void wl_icccm_free_command(char **argv) { (void)argv; }
/* ------------------------------------------------------------------ */
/* Fake leader */
/* ------------------------------------------------------------------ */
static WNativeWindow wl_fake_leader_create(WScreen *scr) { (void)scr; return WNATIVE_NONE; }
static void wl_fake_leader_destroy(WNativeWindow win) { (void)win; }
/* ------------------------------------------------------------------ */
/* Screen internals */
/* ------------------------------------------------------------------ */
/* Wayland compositor is always the sole WM — succeed unconditionally. */
static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; }
/* No X11 event mask selection needed; succeed. */
static int wl_screen_select_input(WScreen *scr, long event_mask) { (void)scr; (void)event_mask; return 1; }
static void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; }
static void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; }
static void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; }
static void wl_screen_alloc_gcs(WScreen *scr) { (void)scr; }
static void wl_screen_create_internals(WScreen *scr) { (void)scr; }
static void wl_screen_create_pixmaps(WScreen *scr) { (void)scr; }
static void wl_screen_load_tech_font(WScreen *scr) { (void)scr; }
static void wl_screen_show_mini_screenshot(WScreen *scr, RImage *img) { (void)scr; (void)img; }
static RImage *wl_screen_capture_area(WScreen *scr) { (void)scr; return NULL; }
static RImage *wl_screen_capture_window(WScreen *scr, WWindow *wwin) { (void)scr; (void)wwin; return NULL; }
static void wl_refresh_desktop(WScreen *scr) { (void)scr; }
/* ------------------------------------------------------------------ */
/* Monitors */
/* ------------------------------------------------------------------ */
static void
wl_monitors_query(WScreen *scr, WHeadGeometry **heads_out, int *count_out)
{
(void)scr;
if (!wl_state.output) {
*heads_out = NULL;
*count_out = 0;
return;
}
/* Phase 34: return the single headless output as head 0. */
WHeadGeometry *heads = wmalloc(sizeof(WHeadGeometry));
if (wl_state.output_layout) {
const struct wlr_box *box =
wlr_output_layout_get_box(wl_state.output_layout, wl_state.output);
if (box) {
heads[0].x = box->x;
heads[0].y = box->y;
heads[0].width = box->width;
heads[0].height = box->height;
} else {
heads[0].x = heads[0].y = 0;
heads[0].width = (int)wl_state.output->width;
heads[0].height = (int)wl_state.output->height;
}
} else {
heads[0].x = heads[0].y = 0;
heads[0].width = (int)wl_state.output->width;
heads[0].height = (int)wl_state.output->height;
}
*heads_out = heads;
*count_out = 1;
}
static void wl_monitors_select_events(WScreen *scr) { (void)scr; }
/* ------------------------------------------------------------------ */
/* Workspace badge */
/* ------------------------------------------------------------------ */
static RImage *
wl_workspace_badge_render(WScreen *scr, WNativeWindow badge_win,
const char *name, int name_len,
int width, int height)
{ (void)scr; (void)badge_win; (void)name; (void)name_len; (void)width; (void)height; return NULL; }
static void
wl_workspace_badge_update(WScreen *scr, WNativeWindow badge_win, RImage *img)
{ (void)scr; (void)badge_win; (void)img; }
/* ------------------------------------------------------------------ */
/* XKB */
/* ------------------------------------------------------------------ */
static WKeyCode wl_keysym_to_keycode(unsigned long keysym) { (void)keysym; return 0; }
static unsigned long wl_keysym_from_keycode(unsigned int keycode, unsigned int index) { (void)keycode; (void)index; return 0; }
static void wl_xkb_lock_group(int group) { (void)group; }
static int wl_xkb_get_group(void) { return 0; }
/* ------------------------------------------------------------------ */
/* Extensions / cursors / error handlers */
/* ------------------------------------------------------------------ */
static void wl_extensions_init(void) {}
static void wl_cursors_init(void) {}
static WNativeWindow wl_cursor_create_named(const char *name) { (void)name; return WNATIVE_NONE; }
static void wl_shape_combine_mask(WNativeWindow win, WNativeWindow mask) { (void)win; (void)mask; }
static void wl_register_error_handlers(void) {}
/* ------------------------------------------------------------------ */
/* vtable */
/* ------------------------------------------------------------------ */
const WMBackend wl_backend = {
.display_open = wl_display_open,
.display_close = wl_display_close,
.display_screen_count = wl_display_screen_count,
.display_default_screen = wl_display_default_screen,
.atom_intern = wl_atom_intern,
.event_loop_run = wl_event_loop_run,
.screen_open = wl_screen_open,
.screen_close = wl_screen_close,
.frame_create_toplevel = wl_frame_create_toplevel,
.frame_create_child = wl_frame_create_child,
.frame_destroy = wl_frame_destroy,
.frame_configure = wl_frame_configure,
.context_save = wl_context_save,
.context_find = wl_context_find,
.context_delete = wl_context_delete,
.focus_set = wl_focus_set,
.key_grab = wl_key_grab,
.key_ungrab_all = wl_key_ungrab_all,
.button_grab = wl_button_grab,
.button_ungrab_all = wl_button_ungrab_all,
.pointer_grab = wl_pointer_grab,
.pointer_ungrab = wl_pointer_ungrab,
.pointer_warp = wl_pointer_warp,
.pointer_warp_to = wl_pointer_warp_to,
.pointer_query = wl_pointer_query,
.pointer_query_full = wl_pointer_query_full,
.pointer_change_grab = wl_pointer_change_grab,
.keyboard_grab = wl_keyboard_grab,
.keyboard_ungrab = wl_keyboard_ungrab,
.server_grab = wl_server_grab,
.server_ungrab = wl_server_ungrab,
.stacking_raise = wl_stacking_raise,
.stacking_lower = wl_stacking_lower,
.stacking_restack = wl_stacking_restack,
.stacking_query = wl_stacking_query,
.event_flush = wl_event_flush,
.event_flush_expose = wl_event_flush_expose,
.event_sync = wl_event_sync,
.event_pending = wl_event_pending,
.event_drain_motion = wl_event_drain_motion,
.event_discard_enter_leave_until = wl_event_discard_enter_leave_until,
.event_handle_extensions = wl_event_handle_extensions,
.event_randr_update = wl_event_randr_update,
.prop_get_normal_hints = wl_prop_get_normal_hints,
.prop_get_wm_hints = wl_prop_get_wm_hints,
.prop_get_wm_class = wl_prop_get_wm_class,
.window_fetch_name = wl_window_fetch_name,
.window_get_icon_name = wl_window_get_icon_name,
.prop_get_wmaker_menu = wl_prop_get_wmaker_menu,
.prop_get_transient_for = wl_prop_get_transient_for,
.prop_has_wmaker_menu = wl_prop_has_wmaker_menu,
.prop_has_gtk_object_path = wl_prop_has_gtk_object_path,
.prop_set_wm_state = wl_prop_set_wm_state,
.prop_get_wm_state = wl_prop_get_wm_state,
.window_map = wl_window_map,
.window_map_raised = wl_window_map_raised,
.window_map_subwindows = wl_window_map_subwindows,
.window_unmap = wl_window_unmap,
.window_move = wl_window_move,
.window_resize = wl_window_resize,
.window_set_border = wl_window_set_border,
.window_set_border_width = wl_window_set_border_width,
.window_set_save_under = wl_window_set_save_under,
.window_clear = wl_window_clear,
.window_get_rect = wl_window_get_rect,
.window_add_events = wl_window_add_events,
.window_select_events = wl_window_select_events,
.window_shape_clear = wl_window_shape_clear,
.window_shape_set = wl_window_shape_set,
.overlay_draw_line = wl_overlay_draw_line,
.overlay_draw_lines = wl_overlay_draw_lines,
.overlay_draw_rect = wl_overlay_draw_rect,
.overlay_draw_segments = wl_overlay_draw_segments,
.overlay_draw_string = wl_overlay_draw_string,
.overlay_text_width = wl_overlay_text_width,
.window_get_client_attrs = wl_window_get_client_attrs,
.client_get_visual_attrs = wl_client_get_visual_attrs,
.client_adopt_attributes = wl_client_adopt_attributes,
.client_reparent = wl_client_reparent,
.client_synth_configure = wl_client_synth_configure,
.client_add_to_save_set = wl_client_add_to_save_set,
.client_remove_from_save_set = wl_client_remove_from_save_set,
.client_close = wl_client_close,
.client_query_shaped = wl_client_query_shaped,
.client_get_gnustep_attrs = wl_client_get_gnustep_attrs,
.client_get_pid = wl_client_get_pid,
.client_snapshot = wl_client_snapshot,
.coords_translate = wl_coords_translate,
.icccm_set_wm_hints = wl_icccm_set_wm_hints,
.icccm_set_class_hint = wl_icccm_set_class_hint,
.icccm_set_class_hint_if_absent = wl_icccm_set_class_hint_if_absent,
.icccm_set_command = wl_icccm_set_command,
.icccm_get_command = wl_icccm_get_command,
.icccm_free_command = wl_icccm_free_command,
.fake_leader_create = wl_fake_leader_create,
.fake_leader_destroy = wl_fake_leader_destroy,
.screen_replace_wm = wl_screen_replace_wm,
.screen_select_input = wl_screen_select_input,
.screen_set_root_cursor = wl_screen_set_root_cursor,
.screen_set_icon_sizes = wl_screen_set_icon_sizes,
.screen_setup_noticeboard = wl_screen_setup_noticeboard,
.screen_alloc_gcs = wl_screen_alloc_gcs,
.screen_create_internals = wl_screen_create_internals,
.screen_create_pixmaps = wl_screen_create_pixmaps,
.screen_load_tech_font = wl_screen_load_tech_font,
.screen_show_mini_screenshot = wl_screen_show_mini_screenshot,
.screen_capture_area = wl_screen_capture_area,
.screen_capture_window = wl_screen_capture_window,
.refresh_desktop = wl_refresh_desktop,
.monitors_query = wl_monitors_query,
.monitors_select_events = wl_monitors_select_events,
.workspace_badge_render = wl_workspace_badge_render,
.workspace_badge_update = wl_workspace_badge_update,
.keysym_to_keycode = wl_keysym_to_keycode,
.keysym_from_keycode = wl_keysym_from_keycode,
.xkb_lock_group = wl_xkb_lock_group,
.xkb_get_group = wl_xkb_get_group,
.extensions_init = wl_extensions_init,
.cursors_init = wl_cursors_init,
.cursor_create_named = wl_cursor_create_named,
.shape_combine_mask = wl_shape_combine_mask,
.register_error_handlers = wl_register_error_handlers,
};