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

2328 lines
101 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
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <sys/select.h>
#ifdef HAVE_INOTIFY
#include <sys/inotify.h>
#include <limits.h>
#endif
#include <time.h>
#include <pthread.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/headless.h>
#include <wlr/backend/session.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_subcompositor.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 "wmaker-popup-v1-server-protocol.h"
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/xwayland.h>
#include <X11/Xproto.h> /* X_SetInputFocus and other request codes */
#include <wlr/backend/wayland.h>
#include <wlr/backend/multi.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/render/allocator.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/interfaces/wlr_buffer.h> /* wlr_buffer_init/wlr_buffer_impl */
#include <libdrm/drm_fourcc.h>
#include <pixman.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
/* Generated protocol header required by wlr_pointer_constraints_v1.h */
#include "protocols/pointer-constraints-unstable-v1-protocol.h"
#include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_output_management_v1.h>
/* Generated protocol header required by wlr_output_power_management_v1.h */
#include "protocols/wlr-output-power-management-unstable-v1-protocol.h"
#include <wlr/types/wlr_output_power_management_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_text_input_v3.h>
#include <wlr/types/wlr_input_method_v2.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
#include <wlr/types/wlr_xdg_activation_v1.h>
/* Generated protocol header required by wlr_layer_shell_v1.h */
#include "protocols/wlr-layer-shell-unstable-v1-protocol.h"
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_data_control_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
/* wlr_input_inhibitor and wlr_idle (org_kde_kwin_idle) were removed in
* wlroots 0.18 — use wlr_session_lock_v1 / wlr_idle_notify_v1 instead. */
#include <wlr/types/wlr_xdg_foreign_registry.h>
#include <wlr/types/wlr_xdg_foreign_v2.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 <xkbcommon/xkbcommon.h>
#include "../../WindowMaker.h"
#include "../../screen.h"
#include "../../window.h"
#include "../../framewin.h" /* WFrameWindow for frame_paint */
#include "../../texture.h" /* WTexture, wTextureRenderImage */
#include "../../misc.h" /* ShrinkString */
#include "../../actions.h"
#include "../../stacking.h"
#include "../../xinerama.h"
#include "../../xmodifier.h"
#include "../../event.h"
#include "../x11/wmspec.h"
#include "../../icon.h" /* WIcon, TILE_* */
#include "../../workspace.h" /* WWorkspace */
#include "../../dock.h" /* WDock (for clip->collapsed) */
#include "../../appicon.h" /* WAppIcon */
#include "../../defaults.h" /* wDefaultsCheckDomains */
#include "../../resources.h" /* wGetColor */
#include "../../menu.h" /* WMenu, WMenuEntry */
#include "../../balloon.h" /* WBalloon */
#include "../../dialog.h" /* WMRestart, WMAbort */
#include "../backend.h"
#include "wl_backend.h"
/* ------------------------------------------------------------------ */
/* WINGs private header — needed for W_EventBackend, W_Screen fields */
#include <WINGs/WINGsP.h>
#include <WINGs/WMEvent.h>
#include "../../def_pixmaps.h"
struct W_Screen;
struct W_View;
void W_WL_EnqueueEvent(struct W_Screen *scr, const WMEvent *ev, struct W_View *target);
void W_WL_SetLoopDispatch(struct W_Screen *scr,
int loop_fd,
void (*dispatch_fn)(int timeout_ms, void *ud),
void *userdata);
void W_RegisterBacking(unsigned long id, void *img);
void W_UnregisterBacking(unsigned long id);
/* ------------------------------------------------------------------ */
/* Forward declarations */
#include "wl_types.h"
extern void wm_dock_app_add(struct wl_toplevel_view *v);
extern void wm_dock_app_remove(WNativeWindow id);
struct wl_compositor_state wl_state;
/* Forward declarations for helpers used before their definition. */
int level_to_bucket(int level);
struct wlr_scene_tree *level_tree_for(int level);
struct wl_frame_buf *frame_buf_find(WNativeWindow id);
/*
* Wayland event loop timer — drives client frame callbacks.
*/
/* Called from within the event loop when the shutdown self-pipe becomes
* readable. Calling wl_display_terminate() here is safe because we are
* inside wl_event_loop_dispatch(), not in a signal handler. */
static int
wl_shutdown_pipe_handler(int fd, uint32_t mask, void *data)
{
char buf[16];
(void)mask; (void)data;
/* drain the pipe so the fd does not stay readable */
while (read(fd, buf, sizeof(buf)) > 0)
;
wl_state.terminate = 1;
return 0;
}
static int
frame_timer_cb(void *data)
{
(void)data;
if (!wl_state.startup_pump_running) {
if (!wl_state.is_nested && 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)))
#if USE_XWAYLAND
/* X error handler — defined near the vtable at the bottom of this file. */
int wl_catch_xwayland_error(Display *edpy, XErrorEvent *error);
#endif
void wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp);
int wl_client_get_pid(WNativeWindow win);
void wl_update_pointer_focus(uint32_t time_msec);
void wl_pointer_warp_to(int x, int y);
/* wmaker-popup-v1 helper — defined in protocol section below. */
void wmaker_popup_apply(struct wl_toplevel_view *view);
extern void wmaker_popup_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id);
extern void handle_pointer_motion(struct wl_listener *listener, void *data);
extern struct wlr_surface *view_surface_at(int px, int py, double *sx_out,
double *sy_out, struct wl_toplevel_view **view_out);
extern void handle_seat_request_cursor(struct wl_listener *listener, void *data);
static void handle_seat_request_set_selection(struct wl_listener *listener, void *data);
void handle_seat_request_start_drag(struct wl_listener *listener, void *data);
static void handle_seat_request_primary_selection(struct wl_listener *listener, void *data);
/* Compositor-internal clipboard hooks for WINGs. */
void wl_clipboard_store_to_seat(const char *text, int len);
char *wl_clipboard_fetch_from_seat(int *len_out);
/* Drag icon overlay for WINGs compositor-internal DND. */
void wl_drag_icon_show(struct W_View *view, int x, int y);
void wl_drag_icon_move(int x, int y);
void wl_drag_icon_hide(void);
void wl_drag_cursor_feedback(Bool accepted);
static void handle_idle_new_inhibitor(struct wl_listener *listener, void *data);
extern void handle_pointer_axis(struct wl_listener *listener, void *data);
static void handle_new_pointer_constraint(struct wl_listener *listener, void *data);
static void handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data);
extern void handle_output_mgr_apply(struct wl_listener *listener, void *data);
extern void handle_output_mgr_test(struct wl_listener *listener, void *data);
extern void handle_output_power_set_mode(struct wl_listener *listener, void *data);
extern void handle_text_input_new(struct wl_listener *listener, void *data);
extern void handle_input_method_new(struct wl_listener *listener, void *data);
extern void handle_virtual_keyboard_new(struct wl_listener *listener, void *data);
extern void handle_xdg_activation_request(struct wl_listener *listener, void *data);
static void handle_layer_shell_new_surface(struct wl_listener *listener, void *data);
extern void handle_xdg_new_toplevel(struct wl_listener *listener, void *data);
extern void handle_new_toplevel_decoration(struct wl_listener *listener, void *data);
extern void handle_virtual_pointer_new(struct wl_listener *listener, void *data);
#ifdef USE_XWAYLAND
extern void handle_xwayland_new_surface(struct wl_listener *listener, void *data);
extern void handle_xwayland_ready(struct wl_listener *listener, void *data);
extern void handle_xwayland_server_destroy(struct wl_listener *listener, void *data);
#endif
extern void wm_dock_create(WScreen *scr);
extern void handle_backend_new_input(struct wl_listener *listener, void *data);
extern void handle_backend_new_output(struct wl_listener *listener, void *data);
extern void handle_output_frame(struct wl_listener *listener, void *data);
extern void handle_pointer_motion_absolute(struct wl_listener *listener, void *data);
extern void handle_pointer_button(struct wl_listener *listener, void *data);
extern void handle_keyboard_key(struct wl_listener *listener, void *data);
extern void handle_keyboard_modifiers(struct wl_listener *listener, void *data);
extern void handle_pointer_frame(struct wl_listener *listener, void *data);
extern void wl_grab_begin_move(struct wl_toplevel_view *view);
extern void wl_grab_begin_resize(struct wl_toplevel_view *view, uint32_t edges);
extern void wl_grab_end(void);
extern const char *resize_edges_to_cursor(uint32_t edges);
/* Startup pump thread: keeps the Wayland event loop running while the main
* thread initialises WINGs/RContext during StartUp(). XWayland needs the
* Wayland compositor event loop to be responsive so it can answer X11
* round-trips like XGetModifierMapping (which queries keyboard state from
* wlroots via Wayland protocol). The thread does NOT touch Xlib — the
* x11_event_source fd watcher is registered AFTER this thread exits in
* wl_event_loop_run(), preventing any concurrent Xlib access. */
static void *
wl_startup_pump_thread(void *arg)
{
(void)arg;
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
while (wl_state.startup_pump_running) {
wl_display_flush_clients(wl_state.display);
wl_event_loop_dispatch(loop, 1);
}
return NULL;
}
#if USE_XWAYLAND
/* Called by the Wayland event loop whenever the X11 connection fd is readable. */
static int
wl_handle_x11_events(int fd, uint32_t mask, void *data)
{
(void)fd; (void)mask; (void)data;
if (!wl_state.x_display)
return 0;
XEvent event;
while (XPending(wl_state.x_display)) {
XNextEvent(wl_state.x_display, &event);
/* Guard: WINGs screen is not initialised until StartUp() completes. */
if (w_global.screen_count > 0) {
/* Translate XEvent → WMEvent at the XWayland boundary.
* _x11_raw carries the original XEvent so WMHandleEvent can
* do X11-specific operations (view lookup, XFilterEvent, etc.) */
WMEvent wev;
memset(&wev, 0, sizeof(wev));
wev._x11_raw = &event;
wev.window = (WNativeWindow)event.xany.window;
switch (event.type) {
case KeyPress: wev.type = WME_KEY_PRESS; break;
case KeyRelease: wev.type = WME_KEY_RELEASE; break;
case ButtonPress: wev.type = WME_BUTTON_PRESS; break;
case ButtonRelease: wev.type = WME_BUTTON_RELEASE; break;
case MotionNotify: wev.type = WME_MOTION; break;
case EnterNotify: wev.type = WME_ENTER; break;
case LeaveNotify: wev.type = WME_LEAVE; break;
case Expose: wev.type = WME_EXPOSE; break;
case ConfigureNotify: wev.type = WME_CONFIGURE; break;
case MapNotify: wev.type = WME_MAP; break;
case UnmapNotify: wev.type = WME_UNMAP; break;
case DestroyNotify: wev.type = WME_DESTROY; break;
case FocusIn: wev.type = WME_FOCUS_IN; break;
case FocusOut: wev.type = WME_FOCUS_OUT; break;
case PropertyNotify: wev.type = WME_PROPERTY; break;
case ClientMessage: wev.type = WME_CLIENT_MESSAGE; break;
case SelectionNotify: wev.type = WME_SELECTION; break;
case SelectionRequest: wev.type = WME_SELECTION_REQUEST; break;
case SelectionClear: wev.type = WME_SELECTION_CLEAR; break;
case MappingNotify: wev.type = WME_MAPPING; break;
case ColormapNotify: wev.type = WME_COLORMAP; break;
case VisibilityNotify: wev.type = WME_VISIBILITY; break;
case ReparentNotify: wev.type = WME_REPARENT; break;
case ConfigureRequest: wev.type = WME_CONFIGURE_REQUEST; break;
case MapRequest: wev.type = WME_MAP_REQUEST; break;
default: wev.type = WME_NONE; break;
}
WMHandleEvent(&wev);
}
}
return 0;
}
#else
static int
wl_handle_x11_events(int fd, uint32_t mask, void *data)
{ (void)fd; (void)mask; (void)data; return 0; }
#endif
void *
wl_display_open(const char *name)
{
const char *sock;
(void)name; /* Wayland compositor picks its own socket name */
/* Initialize listener links so wl_list_remove is safe before first add */
wl_list_init(&wl_state.real_keyboard_key_listener.link);
wl_list_init(&wl_state.real_keyboard_modifiers_listener.link);
wl_list_init(&wl_state.real_keyboard_destroy_listener.link);
wl_list_init(&wl_state.pointer_motion_listener.link);
wl_list_init(&wl_state.real_pointer_motion_abs_listener.link);
wl_list_init(&wl_state.real_pointer_button_listener.link);
wl_list_init(&wl_state.real_pointer_frame_listener.link);
wl_list_init(&wl_state.real_pointer_axis_listener.link);
wl_list_init(&wl_state.real_pointer_destroy_listener.link);
#if USE_XWAYLAND
XInitThreads();
/* Install X error handler: Mesa/EGL via XWayland */
XSetErrorHandler(wl_catch_xwayland_error);
#endif
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;
}
/* choose backend — nested Wayland if WMAKER_WAYLAND_NESTED=1
* is set (run inside an existing Wayland compositor), otherwise headless.
* Both modes always include a headless backend (headless_inner) so that
* wlr_headless_add_input_device works regardless of nesting. */
/* Auto-detect nested mode: if WAYLAND_DISPLAY is set we are running inside
* an existing Wayland session and should use wlr_wl_backend so the
* compositor appears as a window on the host desktop.
* WMAKER_WAYLAND_NESTED=0 can be used to force headless even when
* WAYLAND_DISPLAY is present (e.g. for automated testing). */
{
const char *nested_env = getenv("WMAKER_WAYLAND_NESTED");
const char *wayland_display = getenv("WAYLAND_DISPLAY");
if (nested_env && nested_env[0] == '0')
wl_state.is_nested = 0; /* explicit override: force headless */
else if (nested_env && nested_env[0])
wl_state.is_nested = 1; /* explicit override: force nested */
else
wl_state.is_nested = (wayland_display && wayland_display[0]) ? 1 : 0;
}
/* Always create a headless backend for the output in headless mode. */
wl_state.headless_inner = wlr_headless_backend_create(
wl_display_get_event_loop(wl_state.display));
if (!wl_state.headless_inner) {
wwarning("wl_backend: failed to create headless backend");
wl_display_destroy(wl_state.display);
wl_state.display = NULL;
return NULL;
}
if (wl_state.is_nested) {
/* Nested: multi-backend wrapping nested Wayland + headless. */
wl_state.wl_backend_inner = wlr_wl_backend_create(
wl_display_get_event_loop(wl_state.display), NULL);
if (!wl_state.wl_backend_inner) {
wwarning("wl_backend: failed to create nested Wayland backend; falling back to standalone");
wl_state.is_nested = 0;
}
}
if (wl_state.is_nested && wl_state.wl_backend_inner) {
struct wlr_backend *multi = wlr_multi_backend_create(
wl_display_get_event_loop(wl_state.display));
wlr_multi_backend_add(multi, wl_state.wl_backend_inner);
wlr_multi_backend_add(multi, wl_state.headless_inner);
wl_state.backend = multi;
wwarning("wl_backend: nested Wayland compositor mode");
} else {
/* Standalone: use autocreate to get DRM/KMS + libinput on real hardware.
* Clear DISPLAY and WAYLAND_DISPLAY so wlroots goes straight to DRM. */
unsetenv("DISPLAY");
unsetenv("WAYLAND_DISPLAY");
struct wlr_session *session = NULL;
wl_state.backend = wlr_backend_autocreate(
wl_display_get_event_loop(wl_state.display), &session);
wl_state.session = session;
if (!wl_state.backend) {
wwarning("wl_backend: wlr_backend_autocreate failed; falling back to headless");
wl_state.backend = wl_state.headless_inner;
} else {
wwarning("wl_backend: standalone DRM/KMS compositor mode");
}
}
/* Renderer: use the appropriate backend as source. */
{
struct wlr_backend *render_src;
if (wl_state.wl_backend_inner)
render_src = wl_state.wl_backend_inner;
else
render_src = wl_state.backend;
wl_state.renderer = wlr_renderer_autocreate(render_src);
}
if (!wl_state.renderer) {
wwarning("wl_backend: failed to create renderer");
wlr_backend_destroy(wl_state.backend);
wl_display_destroy(wl_state.display);
wl_state.backend = NULL;
wl_state.display = NULL;
return NULL;
}
/* Allocator — needed for wlr_output_init_render. */
{
struct wlr_backend *alloc_src = wl_state.wl_backend_inner
? wl_state.wl_backend_inner : wl_state.backend;
wl_state.allocator = wlr_allocator_autocreate(alloc_src, wl_state.renderer);
}
if (!wl_state.allocator)
wwarning("wl_backend: allocator unavailable; wlr_output_init_render will be skipped");
/* output layout for multi-head geometry tracking.
* Must be created before backend start so handle_backend_new_output
* can add the output to the layout when it fires. */
wl_state.output_layout = wlr_output_layout_create(wl_state.display);
/* create hardware cursor tracker and load xcursor theme.
* wlr_cursor tracks the logical cursor position across the output
* layout and communicates cursor image changes to each output.
* In nested Wayland mode this calls wl_pointer.set_cursor on the host
* compositor so the cursor is visible in the window.
* Theme and image are applied in handle_backend_new_output after each
* output is added to the layout, ensuring the scale factor is known. */
wl_state.cursor = wlr_cursor_create();
if (wl_state.cursor) {
wlr_cursor_attach_output_layout(wl_state.cursor, wl_state.output_layout);
{
const char *theme = getenv("XCURSOR_THEME");
const char *size_str = getenv("XCURSOR_SIZE");
unsigned int size = size_str ? (unsigned int)atoi(size_str) : 24;
if (size == 0) size = 24;
wl_state.xcursor_mgr = wlr_xcursor_manager_create(theme, size);
}
if (wl_state.xcursor_mgr)
wlr_xcursor_manager_load(wl_state.xcursor_mgr, 1.0f);
wwarning("wl_backend: cursor created, xcursor theme loaded");
} else {
wwarning("wl_backend: wlr_cursor_create failed; cursor will be invisible");
}
/* scene graph — MUST be created before wlr_backend_start so
* that handle_backend_new_output can create the scene_output immediately
* when the output fires (for nested Wayland, new_output fires during
* wlr_backend_start via the host compositor roundtrip). */
wl_state.scene = wlr_scene_create();
/* Register commit callback for WINGs views (defined later in file) */
{
extern void (*W_ViewCommitBackingFn)(unsigned long id);
extern void wl_view_commit_backing_impl(unsigned long id);
extern void wl_view_destroy_backing_impl(unsigned long id);
W_ViewCommitBackingFn = wl_view_commit_backing_impl;
extern void (*W_ViewDestroyBackingFn)(unsigned long id);
W_ViewDestroyBackingFn = wl_view_destroy_backing_impl;
extern void (*W_ViewSceneNodeSetEnabledFn)(unsigned long id, int enabled);
extern void wl_view_scene_node_set_enabled(unsigned long id, int enabled);
W_ViewSceneNodeSetEnabledFn = wl_view_scene_node_set_enabled;
extern void (*W_ClipboardStoreFn)(const char *text, int len);
extern char *(*W_ClipboardFetchFn)(int *len_out);
W_ClipboardStoreFn = wl_clipboard_store_to_seat;
W_ClipboardFetchFn = wl_clipboard_fetch_from_seat;
extern void (*W_DragIconShowFn)(struct W_View *, int, int);
extern void (*W_DragIconMoveFn)(int, int);
extern void (*W_DragIconHideFn)(void);
W_DragIconShowFn = wl_drag_icon_show;
W_DragIconMoveFn = wl_drag_icon_move;
W_DragIconHideFn = wl_drag_icon_hide;
extern void (*W_DragCursorFeedbackFn)(Bool);
W_DragCursorFeedbackFn = wl_drag_cursor_feedback;
}
if (wl_state.scene) {
wwarning("wl_backend: scene graph created");
/* Create the 9 level-bucket trees in bottom-to-top order
* (WL_LAYER_BACKGROUND first, WL_LAYER_OVERLAY last).
* Every subsequent scene node is parented to one of these
* trees, never directly to scene->tree, so Z-ordering is
* determined entirely by bucket index + intra-bucket order. */
int _li;
for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++)
wl_state.level_trees[_li] = wlr_scene_tree_create(&wl_state.scene->tree);
} else
wwarning("wl_backend: wlr_scene_create failed (no compositing)");
/* wire new_input BEFORE starting the backend so that real
* input devices from the nested Wayland backend (fired during
* wlr_backend_start) are captured before the headless devices are
* added explicitly below. */
wl_state.backend_new_input_listener.notify = handle_backend_new_input;
wl_signal_add(&wl_state.backend->events.new_input,
&wl_state.backend_new_input_listener);
/* Wire new_output BEFORE starting the backend. */
wl_state.backend_new_output_listener.notify = handle_backend_new_output;
wl_signal_add(&wl_state.backend->events.new_output,
&wl_state.backend_new_output_listener);
/* For nested mode, queue an output window before start; wlr_wl_output_create
* returns NULL before the backend is initialised — the output is created
* during wlr_backend_start and delivered via new_output. */
if (wl_state.is_nested && wl_state.wl_backend_inner)
wlr_wl_output_create(wl_state.wl_backend_inner);
if (!wlr_backend_start(wl_state.backend)) {
wwarning("wl_backend: failed to start 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;
}
/* For headless mode, add the output explicitly after start; this fires
* handle_backend_new_output synchronously (scene already exists). */
if (!wl_state.is_nested)
wlr_headless_add_output(wl_state.headless_inner, 1920, 1080);
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 DISPLAY=%s",
wl_state.socket_name,
getenv("DISPLAY") ? getenv("DISPLAY") : "(unset)");
wl_state.compositor =
wlr_compositor_create(wl_state.display, 6, wl_state.renderer);
wlr_subcompositor_create(wl_state.display);
wl_state.seat = wlr_seat_create(wl_state.display, "seat0");
wl_state.data_device_mgr =
wlr_data_device_manager_create(wl_state.display);
if (wl_state.seat) {
wl_state.seat_request_cursor_listener.notify = handle_seat_request_cursor;
wl_signal_add(&wl_state.seat->events.request_set_cursor,
&wl_state.seat_request_cursor_listener);
}
/* handle clipboard set-selection requests on the seat so
* Ctrl-C / copy operations from clients propagate to the seat selection
* and become available for paste in other clients. */
if (wl_state.seat) {
wl_state.seat_request_set_selection_listener.notify =
handle_seat_request_set_selection;
wl_signal_add(&wl_state.seat->events.request_set_selection,
&wl_state.seat_request_set_selection_listener);
wwarning("wl_backend: clipboard selection wired");
wl_state.seat_request_start_drag_listener.notify =
handle_seat_request_start_drag;
wl_signal_add(&wl_state.seat->events.request_start_drag,
&wl_state.seat_request_start_drag_listener);
wwarning("wl_backend: DND start_drag wired");
}
/* primary selection (middle-click paste) via
* zwp_primary_selection_device_manager_v1. */
wl_state.primary_selection_mgr =
wlr_primary_selection_v1_device_manager_create(wl_state.display);
if (wl_state.primary_selection_mgr && wl_state.seat) {
wl_state.seat_request_primary_selection_listener.notify =
handle_seat_request_primary_selection;
wl_signal_add(&wl_state.seat->events.request_set_primary_selection,
&wl_state.seat_request_primary_selection_listener);
wwarning("wl_backend: primary selection (middle-click) wired");
}
/* xdg-output — panels and HiDPI apps query logical output
* geometry via zxdg_output_manager_v1. Requires output_layout. */
if (wl_state.output_layout) {
wl_state.xdg_output_mgr =
wlr_xdg_output_manager_v1_create(wl_state.display,
wl_state.output_layout);
if (wl_state.xdg_output_mgr)
wwarning("wl_backend: xdg-output-manager created");
}
/* idle-inhibit — video players and games create inhibitor
* objects to suppress screen blanking while active. */
wl_state.idle_inhibit_mgr =
wlr_idle_inhibit_v1_create(wl_state.display);
if (wl_state.idle_inhibit_mgr) {
wl_state.idle_new_inhibitor_listener.notify = handle_idle_new_inhibitor;
wl_signal_add(&wl_state.idle_inhibit_mgr->events.new_inhibitor,
&wl_state.idle_new_inhibitor_listener);
wwarning("wl_backend: idle-inhibit manager created");
}
/* wp_viewporter — hardware video scaling and subsurface damage
* subset used by mpv, Chromium, Firefox, SDL2. */
wl_state.viewporter = wlr_viewporter_create(wl_state.display);
if (wl_state.viewporter)
wwarning("wl_backend: wp_viewporter created");
else
wwarning("wl_backend: wp_viewporter unavailable");
wl_state.relative_pointer_mgr =
wlr_relative_pointer_manager_v1_create(wl_state.display);
if (wl_state.relative_pointer_mgr)
wwarning("wl_backend: relative-pointer-manager created");
wl_state.pointer_constraints =
wlr_pointer_constraints_v1_create(wl_state.display);
if (wl_state.pointer_constraints) {
wl_state.new_constraint_listener.notify = handle_new_pointer_constraint;
wl_signal_add(&wl_state.pointer_constraints->events.new_constraint,
&wl_state.new_constraint_listener);
wwarning("wl_backend: pointer-constraints created");
}
/* keyboard shortcuts inhibitor — fullscreen apps / remote desktop
* request to suppress WM keyboard shortcuts. */
wl_state.shortcuts_inhibit_mgr =
wlr_keyboard_shortcuts_inhibit_v1_create(wl_state.display);
if (wl_state.shortcuts_inhibit_mgr) {
wl_state.new_shortcuts_inhibitor_listener.notify =
handle_new_shortcuts_inhibitor;
wl_signal_add(&wl_state.shortcuts_inhibit_mgr->events.new_inhibitor,
&wl_state.new_shortcuts_inhibitor_listener);
wwarning("wl_backend: keyboard-shortcuts-inhibit created");
}
/* GPU zero-copy (linux-dmabuf), screencopy, export-dmabuf.
* All three are purely passive globals: wlroots handles the protocol
* implementation internally; no event listeners are required. */
wl_state.linux_dmabuf =
wlr_linux_dmabuf_v1_create_with_renderer(wl_state.display, 4, wl_state.renderer);
if (wl_state.linux_dmabuf)
wwarning("wl_backend: linux-dmabuf created (zwp_linux_dmabuf_v1)");
else
wwarning("wl_backend: linux-dmabuf unavailable (renderer has no DRM FD)");
wl_state.screencopy_mgr =
wlr_screencopy_manager_v1_create(wl_state.display);
if (wl_state.screencopy_mgr)
wwarning("wl_backend: screencopy manager created (zwlr_screencopy_manager_v1)");
wl_state.export_dmabuf_mgr =
wlr_export_dmabuf_manager_v1_create(wl_state.display);
if (wl_state.export_dmabuf_mgr)
wwarning("wl_backend: export-dmabuf manager created (zwlr_export_dmabuf_manager_v1)");
/* output management (wlr-output-management), output power (DPMS),
* gamma control (Night Light / redshift), and presentation time feedback. */
wl_state.output_mgr = wlr_output_manager_v1_create(wl_state.display);
if (wl_state.output_mgr) {
wl_state.output_mgr_apply_listener.notify = handle_output_mgr_apply;
wl_signal_add(&wl_state.output_mgr->events.apply,
&wl_state.output_mgr_apply_listener);
wl_state.output_mgr_test_listener.notify = handle_output_mgr_test;
wl_signal_add(&wl_state.output_mgr->events.test,
&wl_state.output_mgr_test_listener);
wwarning("wl_backend: output-management created (zwlr_output_management_v1)");
}
wl_state.output_power_mgr = wlr_output_power_manager_v1_create(wl_state.display);
if (wl_state.output_power_mgr) {
wl_state.output_power_set_mode_listener.notify = handle_output_power_set_mode;
wl_signal_add(&wl_state.output_power_mgr->events.set_mode,
&wl_state.output_power_set_mode_listener);
wwarning("wl_backend: output-power-management created (zwlr_output_power_management_v1)");
}
wl_state.gamma_control_mgr = wlr_gamma_control_manager_v1_create(wl_state.display);
if (wl_state.gamma_control_mgr)
wwarning("wl_backend: gamma-control created (zwlr_gamma_control_manager_v1)");
wl_state.presentation = wlr_presentation_create(wl_state.display, wl_state.backend, 1);
if (wl_state.presentation)
wwarning("wl_backend: presentation-time created (wp_presentation)");
/* text input v3, input method v2, virtual keyboard v1.
* All three are passive globals for IME / on-screen keyboard support. */
wl_state.text_input_mgr =
wlr_text_input_manager_v3_create(wl_state.display);
if (wl_state.text_input_mgr) {
wl_state.text_input_new_listener.notify = handle_text_input_new;
wl_signal_add(&wl_state.text_input_mgr->events.text_input,
&wl_state.text_input_new_listener);
wwarning("wl_backend: text-input-v3 created (zwp_text_input_manager_v3)");
}
wl_state.input_method_mgr =
wlr_input_method_manager_v2_create(wl_state.display);
if (wl_state.input_method_mgr) {
wl_state.input_method_new_listener.notify = handle_input_method_new;
wl_signal_add(&wl_state.input_method_mgr->events.input_method,
&wl_state.input_method_new_listener);
wwarning("wl_backend: input-method-v2 created (zwp_input_method_manager_v2)");
}
wl_state.virtual_keyboard_mgr =
wlr_virtual_keyboard_manager_v1_create(wl_state.display);
if (wl_state.virtual_keyboard_mgr) {
wl_state.virtual_keyboard_new_listener.notify = handle_virtual_keyboard_new;
wl_signal_add(&wl_state.virtual_keyboard_mgr->events.new_virtual_keyboard,
&wl_state.virtual_keyboard_new_listener);
wwarning("wl_backend: virtual-keyboard-v1 created (zwp_virtual_keyboard_manager_v1)");
}
/* foreign toplevel management — lets taskbars enumerate windows.
* xdg_activation — focus-token protocol replacing _NET_ACTIVE_WINDOW. */
wl_state.foreign_toplevel_mgr =
wlr_foreign_toplevel_manager_v1_create(wl_state.display);
if (wl_state.foreign_toplevel_mgr)
wwarning("wl_backend: foreign-toplevel-management created (zwlr_foreign_toplevel_management_v1)");
wl_state.xdg_activation = wlr_xdg_activation_v1_create(wl_state.display);
if (wl_state.xdg_activation) {
wl_state.xdg_activation_request_listener.notify = handle_xdg_activation_request;
wl_signal_add(&wl_state.xdg_activation->events.request_activate,
&wl_state.xdg_activation_request_listener);
wwarning("wl_backend: xdg-activation created (xdg_activation_v1)");
}
wl_state.layer_shell = wlr_layer_shell_v1_create(wl_state.display, 4);
if (wl_state.layer_shell) {
wl_state.layer_shell_new_surface_listener.notify =
handle_layer_shell_new_surface;
wl_signal_add(&wl_state.layer_shell->events.new_surface,
&wl_state.layer_shell_new_surface_listener);
wwarning("wl_backend: layer-shell created (zwlr_layer_shell_v1)");
}
/* clipboard manager (wl-clipboard/cliphist), input inhibitor
* (lock screen input gating), virtual pointer (remote desktop / VNC),
* KDE idle protocol, xdg-foreign v2 (cross-client window embedding). */
wl_state.data_control_mgr =
wlr_data_control_manager_v1_create(wl_state.display);
if (wl_state.data_control_mgr)
wwarning("wl_backend: data-control created (zwlr_data_control_manager_v1)");
/* zwlr_input_inhibitor_v1 removed from wlroots 0.18 — use ext-session-lock-v1 */
wl_state.virtual_pointer_mgr =
wlr_virtual_pointer_manager_v1_create(wl_state.display);
if (wl_state.virtual_pointer_mgr) {
wl_state.virtual_pointer_new_listener.notify =
handle_virtual_pointer_new;
wl_signal_add(&wl_state.virtual_pointer_mgr->events.new_virtual_pointer,
&wl_state.virtual_pointer_new_listener);
wwarning("wl_backend: virtual-pointer created (zwlr_virtual_pointer_manager_v1)");
}
/* org_kde_kwin_idle (wlr_idle) removed from wlroots 0.18 — use ext-idle-notify-v1 */
wl_state.xdg_foreign_registry =
wlr_xdg_foreign_registry_create(wl_state.display);
if (wl_state.xdg_foreign_registry) {
wl_state.xdg_foreign_v2 =
wlr_xdg_foreign_v2_create(wl_state.display,
wl_state.xdg_foreign_registry);
if (wl_state.xdg_foreign_v2)
wwarning("wl_backend: xdg-foreign-v2 created (zxdg_exporter/importer_v2)");
}
wl_state.xdg_shell = wlr_xdg_shell_create(wl_state.display, 3);
/* Advertise wmaker-popup-v1 — clients use it to drive popup positioning. */
wl_state.wmaker_popup_manager = wl_global_create(wl_state.display,
&wmaker_popup_manager_v1_interface, 1, NULL,
wmaker_popup_manager_bind);
if (wl_state.wmaker_popup_manager)
wwarning("wl_backend: wmaker-popup-v1 manager created");
/* advertise zxdg_decoration_manager_v1 so clients that
* support server-side decorations can negotiate; we respond with
* CLIENT_SIDE since the Wayland path has no X11 frame rendering. */
wl_state.decoration_manager =
wlr_xdg_decoration_manager_v1_create(wl_state.display);
if (wl_state.decoration_manager) {
wl_state.decoration_new_listener.notify = handle_new_toplevel_decoration;
wl_signal_add(&wl_state.decoration_manager->events.new_toplevel_decoration,
&wl_state.decoration_new_listener);
wwarning("wl_backend: xdg-decoration-manager created");
}
wlr_renderer_init_wl_shm(wl_state.renderer, wl_state.display);
/* wl_output global is created in handle_backend_new_output. */
/* 53: In wlroots 0.19, the headless backend has no input devices.
* Real keyboard/pointer devices arrive via handle_backend_new_input when
* the nested Wayland backend starts. We pre-create the default XKB keymap
* here so wl_keysym_to_keycode() can resolve key bindings during StartUp()
* before any real keyboard device has been received. */
{
struct xkb_context *xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (xkb_ctx) {
struct xkb_rule_names rules = {
.rules = getenv("XKB_DEFAULT_RULES"),
.model = getenv("XKB_DEFAULT_MODEL"),
.layout = getenv("XKB_DEFAULT_LAYOUT"),
.variant = getenv("XKB_DEFAULT_VARIANT"),
.options = getenv("XKB_DEFAULT_OPTIONS"),
};
wl_state.xkb_keymap = xkb_keymap_new_from_names(
xkb_ctx, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
xkb_context_unref(xkb_ctx);
if (wl_state.xkb_keymap) {
wwarning("wl_backend: default XKB keymap created for key binding resolution");
/* Apply to any keyboard that was wired before keymap was ready,
* but only if it doesn't already have one from the host. */
if (wl_state.keyboard_dev) {
struct wlr_keyboard *kb = wlr_keyboard_from_input_device(wl_state.keyboard_dev);
if (!kb->xkb_state)
wlr_keyboard_set_keymap(kb, wl_state.xkb_keymap);
}
}
}
}
/* Advertise keyboard+pointer capability unconditionally so clients do not
* need to wait for a real device to appear before binding wl_keyboard / wl_pointer. */
if (wl_state.seat)
wlr_seat_set_capabilities(wl_state.seat,
WL_SEAT_CAPABILITY_KEYBOARD | WL_SEAT_CAPABILITY_POINTER);
wl_list_init(&wl_state.toplevel_list);
wl_list_init(&wl_state.or_list);
wl_list_init(&wl_state.frame_list);
wl_list_init(&wl_state.layer_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;
wl_state.xdg_new_surface_listener.notify = handle_xdg_new_toplevel;
wl_signal_add(&wl_state.xdg_shell->events.new_toplevel,
&wl_state.xdg_new_surface_listener);
/* Path 1: XWayland bridge — start Xwayland eagerly (lazy=false) so that
* the compositor can open a Display* from its socket and pass it back to
* main.c. WindowMaker's own X11 UI (title bars, root menus, dock) then
* renders through XWayland, making the WM visible on-screen.
* The ready signal fires once Xwayland's socket accepts connections. */
wl_state.xwayland = wlr_xwayland_create(wl_state.display,
wl_state.compositor, false);
#ifdef USE_XWAYLAND
if (wl_state.xwayland) {
if (wl_state.xwayland->server) {
wl_state.xwayland_server_destroy_listener.notify =
handle_xwayland_server_destroy;
wl_signal_add(&wl_state.xwayland->server->events.destroy,
&wl_state.xwayland_server_destroy_listener);
}
wl_state.xwayland_ready_listener.notify = handle_xwayland_ready;
wl_signal_add(&wl_state.xwayland->events.ready,
&wl_state.xwayland_ready_listener);
wl_state.xwayland_new_surface_listener.notify = handle_xwayland_new_surface;
wl_signal_add(&wl_state.xwayland->events.new_surface,
&wl_state.xwayland_new_surface_listener);
wlr_xwayland_set_seat(wl_state.xwayland, wl_state.seat);
if (wl_state.xwayland->display_name) {
setenv("DISPLAY", wl_state.xwayland->display_name, 1);
wwarning("wl_backend: XWayland starting DISPLAY=%s (eager mode)",
wl_state.xwayland->display_name);
} else {
wwarning("wl_backend: XWayland created (display_name pending)");
}
} else {
wwarning("wl_backend: XWayland unavailable (X11 clients will not work)");
}
#endif
/* 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.pointer_x = 960;
wl_state.pointer_y = 540;
wwarning("wl_backend: virtual pointer initialised at %d,%d",
wl_state.pointer_x, wl_state.pointer_y);
/* Self-pipe shutdown: signal handler writes a byte; the event loop reads
* it here and calls wl_display_terminate() from within loop context,
* avoiding the async-signal-safety issues of calling wl_display_destroy()
* directly from a signal handler. */
wl_state.shutdown_pipe[0] = wl_state.shutdown_pipe[1] = -1;
wl_state.shutdown_source = NULL;
wl_state.sigfd = -1;
wl_state.sigfd_source = NULL;
if (pipe(wl_state.shutdown_pipe) == 0) {
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
/* Make read end non-blocking so the drain loop in the handler returns
* instead of blocking after all bytes are consumed. */
fcntl(wl_state.shutdown_pipe[0], F_SETFL, O_NONBLOCK);
wl_state.shutdown_source = wl_event_loop_add_fd(loop,
wl_state.shutdown_pipe[0],
WL_EVENT_READABLE,
wl_shutdown_pipe_handler,
NULL);
} else {
wwarning("wl_backend: shutdown pipe creation failed; SIGTERM may crash");
}
/* if a real keyboard was found from the nested backend
* (captured in handle_backend_new_input during wlr_backend_start),
* switch the seat to it now so keyboard focus notifications use the
* correct keymap and modifier state from the host compositor. This
* must happen after the headless keyboard setup above (which may have
* called wlr_seat_set_keyboard with the headless device). */
if (wl_state.real_keyboard_dev)
wlr_seat_set_keyboard(wl_state.seat,
wlr_keyboard_from_input_device(wl_state.real_keyboard_dev));
wl_state.started = 1;
/* Block until XWayland fires the "ready" signal — pump the Wayland server
* event loop so the XWayland process can complete its handshake.
* handle_xwayland_ready() calls XOpenDisplay and stores the result in
* wl_state.x_display, then sets xwayland_ready = true to break the loop.
* If XWayland is not available, return NULL (pure Wayland path). */
/* XWayland is lazy — it starts when the first X11 client connects.
* No need to wait for it at startup. Mark as ready so the rest of
* init proceeds without blocking. */
if (wl_state.xwayland) {
wwarning("wl_backend: XWayland configured (eager mode) — ready at startup");
wl_state.xwayland_ready = true;
if (wl_state.x_display) {
wwarning("wl_backend: XWayland bridge ready — Display*=%p, full WM UI enabled",
(void *)wl_state.x_display);
/* Start a pump thread to keep the Wayland event loop running
* while the main thread calls WMCreateScreenWithRContext() and
* other blocking X11 init in StartUp(). XWayland needs the
* Wayland compositor to be responsive to handle round-trips like
* XGetModifierMapping (which queries keyboard state from wlroots).
* The x11_event_source fd watcher is registered later in
* wl_event_loop_run() after this thread exits, so the pump
* thread never touches Xlib and there is no Xlib contention. */
wl_state.startup_pump_running = 1;
if (pthread_create(&wl_state.startup_pump_thread, NULL,
wl_startup_pump_thread, NULL) != 0) {
wwarning("wl_backend: pthread_create for pump thread failed — "
"WINGs init may deadlock");
wl_state.startup_pump_running = 0;
}
} else {
wwarning("wl_backend: XWayland ready but XOpenDisplay failed — pure Wayland mode");
}
}
return NULL; /* dpy=NULL so WM core skips Xlib calls */
}
void
wl_display_close(void)
{
if (!wl_state.started)
return;
/* Remove the X11 event source and close the Xlib bridge connection
* before killing XWayland so we don't get stale readable events. */
if (wl_state.x11_event_source) {
wl_event_source_remove(wl_state.x11_event_source);
wl_state.x11_event_source = NULL;
}
#if USE_XWAYLAND
if (wl_state.x_display) {
XCloseDisplay(wl_state.x_display);
wl_state.x_display = NULL;
}
#endif
/* shut down XWayland.
*
* wlroots 0.15 bug: wlr_xwayland_server_destroy() calls wl_client_destroy()
* on the XWayland Wayland client. After any Wayland client has connected
* and disconnected during the session, the XWayland server's wl_client ends
* up in an inconsistent state: the client struct is freed but server->client
* is NOT nulled. wlr_xwayland_server_destroy() then calls wl_client_destroy()
* on the freed struct and crashes in wl_list_remove().
*
* Workaround: skip wlr_xwayland_destroy() entirely. The Xwayland process is
* a child of wmaker and will receive SIGHUP/SIGKILL when the parent exits.
* The X11 socket is in a tmpdir removed on exit. wl_display_destroy() (called
* below) handles all remaining Wayland client/resource cleanup.
*/
if (wl_state.xwayland) {
/* wlroots 0.15 bug: wlr_xwayland_server_destroy() (called from the
* display destroy signal below) sends SIGTERM to Xwayland and calls
* waitpid(). If Xwayland is blocked waiting for compositor events,
* waitpid() hangs indefinitely.
*
* Fix: pre-kill Xwayland with SIGKILL. SIGKILL cannot be caught or
* deferred, so the process dies immediately (kernel-synchronous).
* The zombie remains until wlroots' waitpid() reaps it — which now
* returns instantly. The Wayland client struct (server->client) is
* still valid because the event loop is stopped and the socket closure
* has not been dispatched, so wlroots' wl_client_destroy() succeeds. */
if (wl_state.xwayland->server) {
int xdisp = wl_state.xwayland->server->display;
if (wl_state.xwayland->server->pid > 0)
kill(wl_state.xwayland->server->pid, SIGKILL);
/* wlroots 0.15 bug: after any Wayland client has connected and
* disconnected during the session, server->client holds a freed
* (stale) wl_client pointer. wlr_xwayland_server_destroy() (fired
* from the display destroy signal below) calls wl_client_destroy()
* on this freed pointer, which corrupts the list and triggers
* SIGABRT. Null the pointer so wlroots skips the destroy call.
* If the client is still valid, wl_display_destroy_clients() will
* clean it up; if it is stale, it has already been removed from
* the display's client list and nothing more is needed. */
wl_state.xwayland->server->client = NULL;
/* Clean up X lock file and Unix socket left by SIGKILL'd Xwayland.
* Without this, the next run may find a stale lock file whose PID
* has been reused by an unrelated process, causing wlroots to
* refuse to reclaim the display number → "xcb connect failed". */
{
char path[64];
snprintf(path, sizeof(path), "/tmp/.X%d-lock", xdisp);
unlink(path);
snprintf(path, sizeof(path), "/tmp/.X11-unix/X%d", xdisp);
unlink(path);
}
} else if (wl_state.xwayland->display_name &&
wl_state.xwayland->display_name[0] == ':') {
/* Path 1: server exited early (e.g. stale lock) before wl_display_close
* ran — wlroots already freed server and set it to NULL. Clean up
* any lock file that Xwayland created before dying. */
int xdisp = atoi(wl_state.xwayland->display_name + 1);
char path[64];
snprintf(path, sizeof(path), "/tmp/.X%d-lock", xdisp);
unlink(path);
snprintf(path, sizeof(path), "/tmp/.X11-unix/X%d", xdisp);
unlink(path);
}
wl_state.xwayland = NULL;
unsetenv("DISPLAY");
}
/* Clean up the signalfd source (registered in wl_event_loop_run). */
if (wl_state.sigfd_source) {
wl_event_source_remove(wl_state.sigfd_source);
wl_state.sigfd_source = NULL;
}
if (wl_state.sigfd >= 0) {
close(wl_state.sigfd);
wl_state.sigfd = -1;
}
/* Clean up the shutdown self-pipe (remove event source before display
* destruction so the fd is not left in the epoll set). */
if (wl_state.shutdown_source) {
wl_event_source_remove(wl_state.shutdown_source);
wl_state.shutdown_source = NULL;
}
if (wl_state.shutdown_pipe[0] >= 0) {
close(wl_state.shutdown_pipe[0]);
wl_state.shutdown_pipe[0] = -1;
}
if (wl_state.shutdown_pipe[1] >= 0) {
close(wl_state.shutdown_pipe[1]);
wl_state.shutdown_pipe[1] = -1;
}
/* Remove all globally-registered wl_listener nodes before calling
* wl_display_destroy(). wl_display_destroy() frees wlroots objects
* (xdg_shell, backend, keyboard, pointer, output, etc.), which frees
* their signal structs. Any wl_listener.link that is still in one of
* those signal lists after the struct is freed becomes a dangling pointer.
* Subsequent wl_list operations (in wayland-server cleanup or by glibc's
* heap allocator detecting corruption) then trigger SIGABRT.
* Guard each removal with a link.next check: wayland-server zeroes the
* link after wl_list_remove(), so a non-NULL link means the listener is
* currently inserted in a signal list. */
#define WL_LISTENER_REMOVE(l) \
do { if ((l).link.next) wl_list_remove(&(l).link); } while (0)
/* Output frame and new-output listeners */
WL_LISTENER_REMOVE(wl_state.output_frame_listener);
WL_LISTENER_REMOVE(wl_state.backend_new_output_listener);
/* XDG shell and decoration listeners */
WL_LISTENER_REMOVE(wl_state.xdg_new_surface_listener);
WL_LISTENER_REMOVE(wl_state.decoration_new_listener);
/* XWayland listeners (xwayland ptr already nulled in step 1,
* but listener links are still inserted in the (now-orphaned) xwayland
* object's signal lists — remove them to prevent heap corruption). */
WL_LISTENER_REMOVE(wl_state.xwayland_ready_listener);
WL_LISTENER_REMOVE(wl_state.xwayland_new_surface_listener);
WL_LISTENER_REMOVE(wl_state.xwayland_server_destroy_listener);
/* Keyboard listeners */
WL_LISTENER_REMOVE(wl_state.keyboard_key_listener);
WL_LISTENER_REMOVE(wl_state.keyboard_modifiers_listener);
/* Pointer listeners */
WL_LISTENER_REMOVE(wl_state.pointer_motion_listener);
WL_LISTENER_REMOVE(wl_state.pointer_motion_abs_listener);
WL_LISTENER_REMOVE(wl_state.pointer_button_listener);
WL_LISTENER_REMOVE(wl_state.pointer_frame_listener);
WL_LISTENER_REMOVE(wl_state.backend_new_input_listener);
WL_LISTENER_REMOVE(wl_state.real_keyboard_key_listener);
WL_LISTENER_REMOVE(wl_state.real_keyboard_modifiers_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_motion_abs_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_button_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_frame_listener);
WL_LISTENER_REMOVE(wl_state.seat_request_cursor_listener);
WL_LISTENER_REMOVE(wl_state.seat_request_set_selection_listener);
WL_LISTENER_REMOVE(wl_state.seat_request_primary_selection_listener);
WL_LISTENER_REMOVE(wl_state.idle_new_inhibitor_listener);
WL_LISTENER_REMOVE(wl_state.pointer_axis_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_axis_listener);
WL_LISTENER_REMOVE(wl_state.new_constraint_listener);
WL_LISTENER_REMOVE(wl_state.new_shortcuts_inhibitor_listener);
WL_LISTENER_REMOVE(wl_state.output_mgr_apply_listener);
WL_LISTENER_REMOVE(wl_state.output_mgr_test_listener);
WL_LISTENER_REMOVE(wl_state.output_power_set_mode_listener);
WL_LISTENER_REMOVE(wl_state.text_input_new_listener);
WL_LISTENER_REMOVE(wl_state.input_method_new_listener);
WL_LISTENER_REMOVE(wl_state.virtual_keyboard_new_listener);
WL_LISTENER_REMOVE(wl_state.xdg_activation_request_listener);
WL_LISTENER_REMOVE(wl_state.layer_shell_new_surface_listener);
WL_LISTENER_REMOVE(wl_state.virtual_pointer_new_listener);
#undef WL_LISTENER_REMOVE
if (wl_state.xcursor_mgr) {
wlr_xcursor_manager_destroy(wl_state.xcursor_mgr);
wl_state.xcursor_mgr = NULL;
}
if (wl_state.cursor) {
wlr_cursor_destroy(wl_state.cursor);
wl_state.cursor = NULL;
}
/* Skip wl_display_destroy() and wl_display_destroy_clients().
*
* wl_display_destroy() fires the display's destroy signal which triggers
* wlroots 0.15 internal object teardown. That teardown has a double-
* remove bug in the XWayland path: wlroots registers a listener on the
* Xwayland wl_client's destroy signal; that listener self-removes during
* client destruction, and wlr_xwayland_server_destroy() then tries to
* wl_list_remove() the already-nulled link, corrupting the heap
* (SIGABRT).
*
* wl_display_destroy_clients() also blocks: wlroots' XWayland client
* destroy handler performs blocking cleanup that can hang if Xwayland
* is in the middle of protocol dispatch.
*
* Both calls are unnecessary here because Exit() calls exit(0) immediately
* after wl_display_close() returns. The kernel reclaims all resources:
* - file descriptors closed → all Wayland clients get POLLHUP
* - memory freed by OS
* - Xwayland already SIGKILL'd in step 1
* - event sources removed in step 2
* - all our signal listeners removed in step 3 */
unsetenv("WAYLAND_DISPLAY");
wl_state.renderer = NULL;
wl_state.backend = NULL;
wl_state.headless_inner = NULL;
wl_state.wl_backend_inner = NULL;
wl_state.display = NULL;
wl_state.output = NULL;
wl_state.frame_timer = NULL;
wl_state.output_layout = NULL;
wl_state.scene_output = NULL;
wl_state.scene = NULL;
wl_state.allocator = NULL;
wl_state.started = 0;
}
void
wl_display_post_open(const char *display_name, int *multi_head_out)
{
(void)display_name;
(void)multi_head_out;
}
int
wl_display_screen_count(void)
{
return wl_state.started ? 1 : 0;
}
int
wl_display_default_screen(void)
{
return 0;
}
void
wl_event_loop_run(void)
{
if (!wl_state.display)
return;
/*
* Install signalfd as the primary SIGTERM/SIGINT/SIGHUP handler.
*
* The traditional path (startup.c handleExitSig → DispatchEvent →
* event_loop_terminate → pipe write) works in headless/smoke-test mode
* because epoll_wait with SA_RESTART sees the pipe immediately after the
* signal handler returns. In nested-Wayland mode the wlroots nested
* backend processes parent-compositor events alongside our own clients,
* and the combination of SA_RESTART + epoll ordering can cause the pipe
* write to be missed until the next event, leaving SIGTERM apparently
* ignored.
*
* signalfd is the compositor-standard solution: block async delivery,
* route signals through a regular fd in the epoll set, and handle them
* synchronously from within the dispatch loop. The pipe-based path
* (wl_event_loop_terminate) is retained as a fallback for code paths
* that call it before wl_event_loop_run() returns.
*/
{
sigset_t ss;
sigemptyset(&ss);
sigaddset(&ss, SIGTERM);
sigaddset(&ss, SIGINT);
sigaddset(&ss, SIGHUP);
wl_state.sigfd = signalfd(-1, &ss, SFD_NONBLOCK | SFD_CLOEXEC);
if (wl_state.sigfd >= 0) {
/* Block async delivery so signals only arrive via the fd. */
sigprocmask(SIG_BLOCK, &ss, NULL);
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
wl_state.sigfd_source = wl_event_loop_add_fd(loop,
wl_state.sigfd,
WL_EVENT_READABLE,
wl_signalfd_handler,
NULL);
if (wl_state.sigfd_source) {
wwarning("wl_backend: signalfd installed for SIGTERM/SIGINT/SIGHUP");
/* Disable the old signal handlers — signalfd owns these now */
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGHUP, SIG_DFL);
} else {
wwarning("wl_backend: wl_event_loop_add_fd failed; falling back to pipe");
close(wl_state.sigfd);
wl_state.sigfd = -1;
/* Restore async delivery so the old handler still works. */
sigprocmask(SIG_UNBLOCK, &ss, NULL);
}
} else {
wwarning("wl_backend: signalfd(2) failed (%s); SIGTERM via pipe fallback",
strerror(errno));
}
}
/* Stop the startup pump thread — wl_display_run() takes over Wayland
* event dispatching from here. Join before registering x11_event_source
* so there is no concurrent access to the event loop. */
if (wl_state.startup_pump_running) {
wl_state.startup_pump_running = 0;
pthread_join(wl_state.startup_pump_thread, NULL);
wwarning("wl_backend: startup pump thread stopped");
}
/* Deferred dock creation — safe now that the pump thread has stopped.
* wm_dock_create calls wlr_scene_buffer_create which must not race with
* the pump thread's frame-timer wlr_scene_output_commit. */
if (wl_state.dock_pending_scr) {
wm_dock_create(wl_state.dock_pending_scr);
wl_state.dock_pending_scr = NULL;
}
/* Create scene nodes for any OR views whose associate signal fired
* during the pump thread (when scene ops were unsafe). */
{
struct wl_or_view *ov;
wl_list_for_each(ov, &wl_state.or_list, link) {
if (!ov->scene_node && ov->xw_surface->surface && wl_state.scene) {
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU]
? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree;
ov->scene_node = wlr_scene_subsurface_tree_create(_op, ov->xw_surface->surface);
if (ov->scene_node)
wlr_scene_node_set_position(&ov->scene_node->node,
ov->xw_surface->x, ov->xw_surface->y);
}
}
}
/* Register the X11 fd watcher now that StartUp() has completed and
* WINGs is initialised. Doing this here (not in wl_display_open) ensures
* the pump thread never touches Xlib. */
if (wl_state.x_display && !wl_state.x11_event_source) {
wl_state.x11_event_source = wl_event_loop_add_fd(
wl_display_get_event_loop(wl_state.display),
ConnectionNumber(wl_state.x_display),
WL_EVENT_READABLE,
wl_handle_x11_events, NULL);
if (wl_state.x11_event_source)
wwarning("wl_backend: X11 event source registered for WINGs dispatch");
}
wwarning("wl_backend: DISPLAY=%s WAYLAND_DISPLAY=%s",
getenv("DISPLAY") ? getenv("DISPLAY") : "(unset)",
getenv("WAYLAND_DISPLAY") ? getenv("WAYLAND_DISPLAY") : "(unset)");
/* register compositor dispatch callback so WINGs modal loops
* (WMRunModalLoop, color panels, etc.) can tick the event loop. */
{
WMScreen *wmscr = wl_get_wmscreen();
if (wmscr) {
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
int loop_fd = wl_event_loop_get_fd(loop);
W_WL_SetLoopDispatch(wmscr, loop_fd, wl_wings_dispatch, NULL);
wwarning("wl_backend: WINGs loop dispatch registered (fd=%d)", loop_fd);
}
}
wwarning("wl_backend: event loop running");
/* Custom event loop: pump wlroots, then drain WINGs events.
* We can't use wl_display_run() because WM event handlers (OpenRootMenu,
* etc.) call WMMaskEvent which re-enters the wlroots loop. By draining
* WINGs events BETWEEN dispatches (not inside a timer callback), modal
* loops in WMMaskEvent can safely call wl_event_loop_dispatch. */
{
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
while (!wl_state.terminate) {
wl_display_flush_clients(wl_state.display);
extern void W_WLClientDispatchPending(void);
W_WLClientDispatchPending();
wl_event_loop_dispatch(loop, 16);
/* Schedule frame AFTER dispatch so the frame event on the next
* iteration renders content from commits processed above. */
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
/* Check inotify for defaults database changes */
#ifdef HAVE_INOTIFY
if (w_global.inotify.fd_event_queue >= 0) {
fd_set rfds;
struct timeval tv = { 0, 0 };
FD_ZERO(&rfds);
FD_SET(w_global.inotify.fd_event_queue, &rfds);
if (select(w_global.inotify.fd_event_queue + 1, &rfds, NULL, NULL, &tv) > 0
&& FD_ISSET(w_global.inotify.fd_event_queue, &rfds)) {
wl_handle_inotify_events();
}
}
#endif
/* Fire any expired WINGs timers (workspace name fade, etc.) */
W_CheckTimerHandlers();
/* Drain WINGs event queue */
WMScreen *wmscr = wl_get_wmscreen();
if (wmscr) {
const W_EventBackend *be = wmscr->eventBackend;
void *ectx = wmscr->eventBackendCtx;
WMEvent wev;
int limit = 64;
int handled = 0;
while (be->pending(ectx) && limit-- > 0) {
be->fetch(ectx, &wev);
WMHandleEvent(&wev);
handled++;
}
/* If WINGs events were processed (which may trigger client
* surface commits via wl_redisplay), do another short dispatch
* to process those commits so the next frame renders them. */
if (handled)
wl_event_loop_dispatch(loop, 0);
}
}
}
wwarning("wl_backend: event loop exited");
/* Remove pointer listeners before wlroots destroys the pointer device */
if (wl_state.pointer_dev) {
wl_list_remove(&wl_state.real_pointer_motion_abs_listener.link);
wl_list_remove(&wl_state.real_pointer_button_listener.link);
wl_list_remove(&wl_state.real_pointer_frame_listener.link);
wl_list_remove(&wl_state.real_pointer_axis_listener.link);
}
}
void
wl_event_loop_terminate(void)
{
/* Write a byte to the self-pipe. write(2) is async-signal-safe, so this
* can safely be called from a signal handler. The event loop will read
* the pipe and call wl_display_terminate() from within loop context,
* avoiding the double-free that results from calling wl_display_destroy()
* while wl_event_loop_dispatch() is active. */
if (wl_state.shutdown_pipe[1] >= 0) {
char c = 'T';
(void)write(wl_state.shutdown_pipe[1], &c, 1);
} else if (wl_state.display) {
/* fallback if pipe creation failed at startup */
wl_display_terminate(wl_state.display);
}
}
/* ------------------------------------------------------------------ */
/* Drag icon overlay for WINGs compositor-internal DND */
/*
* seat request_set_selection — client calls wl_data_device.set_selection
* (i.e. Ctrl-C or other clipboard write). Forward to wlr_seat so the
* selection becomes available to the next client that reads it.
*/
static void
handle_seat_request_set_selection(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_seat_request_set_selection_event *ev = data;
if (!wl_state.seat)
return;
wlr_seat_set_selection(wl_state.seat, ev->source, ev->serial);
}
/*
* seat request_set_primary_selection — client writes to the
* primary selection buffer (e.g. by selecting text). Forward to
* wlr_seat so middle-click paste works in other clients.
*/
static void
handle_seat_request_primary_selection(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_seat_request_set_primary_selection_event *ev = data;
if (!wl_state.seat)
return;
wlr_seat_set_primary_selection(wl_state.seat, ev->source, ev->serial);
}
/*
* idle_inhibit new_inhibitor — a client (e.g. mpv, a game) created
* an idle-inhibit object. wlroots tracks the inhibitor list internally;
* the compositor can query wl_state.idle_inhibit_mgr->inhibitors to decide
* whether to suppress screen blanking. For now we just log the event so
* the capability is advertised and the inhibitor object doesn't error out.
*/
static void
handle_idle_new_inhibitor(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_idle_inhibitor_v1 *inhibitor = data;
(void)inhibitor;
wwarning("wl_backend: idle inhibitor created (client suppressing screen blank)");
}
/*
* scroll-wheel axis event handler.
* wlr_event_pointer_axis carries orientation (vertical/horizontal), delta,
* delta_discrete (notches), and source (wheel vs finger). Forward directly
* to the seat so the focused Wayland client receives wl_pointer.axis events.
* Without this, scrolling is silently dropped in every Wayland client.
*/
/*
* new pointer constraint — a client requested pointer lock or
* pointer confinement (e.g. a game locking the cursor to its window).
* We activate the constraint immediately so the client gets confirmed.
* In headless mode the pointer is virtual so there is nothing physically
* to lock, but activating keeps the client happy.
*/
static void
handle_new_pointer_constraint(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_pointer_constraint_v1 *constraint = data;
(void)constraint;
/* Activate immediately — in nested mode the real pointer is bounded
* by the host compositor's window anyway. */
wlr_pointer_constraint_v1_send_activated(constraint);
wwarning("wl_backend: pointer constraint activated");
}
/*
* new keyboard shortcuts inhibitor — a fullscreen app or remote
* desktop client wants to suppress WM keyboard shortcuts.
* Activate the inhibitor immediately so the client knows it is granted.
* The existing wWaylandKeyPress() dispatcher in handle_keyboard_key already
* forwards all non-shortcut keys; with the inhibitor active we should skip
* the shortcut check for that focused client. For now we simply activate
* and log; per-client inhibitor tracking can be refined later.
*/
static void
handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data;
wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor);
wwarning("wl_backend: keyboard shortcuts inhibitor activated");
}
#define LAYER_VIEW_FROM(l, field) \
((struct wl_layer_view *)((char *)(l) - offsetof(struct wl_layer_view, field)))
/*
* Compute the geometry for a layer surface given its anchor, margins,
* desired size and the output dimensions.
*/
static void
layer_surface_compute_geometry(struct wlr_layer_surface_v1 *ls,
uint32_t ow, uint32_t oh,
uint32_t *out_w, uint32_t *out_h)
{
const struct wlr_layer_surface_v1_state *s = &ls->current;
uint32_t w = s->desired_width;
uint32_t h = s->desired_height;
bool anchor_lr = (s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) &&
(s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
bool anchor_tb = (s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) &&
(s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
if (w == 0 && anchor_lr)
w = ow - s->margin.left - s->margin.right;
if (h == 0 && anchor_tb)
h = oh - s->margin.top - s->margin.bottom;
/* Ensure minimum 1×1. */
*out_w = w > 0 ? w : 1;
*out_h = h > 0 ? h : 1;
}
/*
* Compute the position of a layer surface on the output given its
* anchor, margins and final dimensions.
*/
static void
layer_surface_compute_position(struct wlr_layer_surface_v1 *ls,
uint32_t ow, uint32_t oh,
uint32_t fw, uint32_t fh,
int *out_x, int *out_y)
{
const struct wlr_layer_surface_v1_state *s = &ls->current;
int x = 0, y = 0;
bool anchor_l = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
bool anchor_r = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
bool anchor_t = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool anchor_b = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
if (anchor_l && anchor_r)
x = s->margin.left;
else if (anchor_l)
x = s->margin.left;
else if (anchor_r)
x = (int)ow - (int)fw - s->margin.right;
else
x = ((int)ow - (int)fw) / 2;
if (anchor_t && anchor_b)
y = s->margin.top;
else if (anchor_t)
y = s->margin.top;
else if (anchor_b)
y = (int)oh - (int)fh - s->margin.bottom;
else
y = ((int)oh - (int)fh) / 2;
*out_x = x;
*out_y = y;
}
static void
handle_layer_surface_map(struct wl_listener *listener, void *data)
{
(void)data;
struct wl_layer_view *lv = LAYER_VIEW_FROM(listener, map_listener);
wwarning("wl_backend: layer surface mapped (layer=%d namespace=%s)",
lv->layer_surface->current.layer,
lv->layer_surface->namespace ? lv->layer_surface->namespace : "");
}
static void
handle_layer_surface_unmap(struct wl_listener *listener, void *data)
{
(void)data;
struct wl_layer_view *lv = LAYER_VIEW_FROM(listener, unmap_listener);
wwarning("wl_backend: layer surface unmapped");
if (lv->scene_node) {
wlr_scene_node_destroy(&lv->scene_node->node);
lv->scene_node = NULL;
}
}
static void
handle_layer_surface_destroy(struct wl_listener *listener, void *data)
{
(void)data;
struct wl_layer_view *lv = LAYER_VIEW_FROM(listener, destroy_listener);
wl_list_remove(&lv->map_listener.link);
wl_list_remove(&lv->unmap_listener.link);
wl_list_remove(&lv->destroy_listener.link);
if (lv->scene_node) {
wlr_scene_node_destroy(&lv->scene_node->node);
lv->scene_node = NULL;
}
wl_list_remove(&lv->link);
free(lv);
}
/*
* handle_layer_shell_new_surface — a client created a layer surface.
* Assign the output if unset, compute geometry, send configure, and
* wire map/unmap/destroy lifecycle.
*/
static void
handle_layer_shell_new_surface(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_layer_surface_v1 *ls = data;
/* Assign output if client did not specify one. */
if (!ls->output && wl_state.output)
ls->output = wl_state.output;
struct wl_layer_view *lv = calloc(1, sizeof(*lv));
if (!lv) {
wlr_layer_surface_v1_destroy(ls);
return;
}
lv->layer_surface = ls;
/* Add to scene graph in the correct level bucket.
* Layer-shell layer enum maps to WL_LAYER_* buckets:
* BACKGROUND -> WL_LAYER_BACKGROUND
* BOTTOM -> WL_LAYER_DESKTOP
* TOP -> WL_LAYER_DOCK
* OVERLAY -> WL_LAYER_OVERLAY */
if (wl_state.scene) {
int _lb;
switch (ls->pending.layer) {
case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: _lb = WL_LAYER_BACKGROUND; break;
case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: _lb = WL_LAYER_DESKTOP; break;
case ZWLR_LAYER_SHELL_V1_LAYER_TOP: _lb = WL_LAYER_DOCK; break;
case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: _lb = WL_LAYER_OVERLAY; break;
default: _lb = WL_LAYER_NORMAL; break;
}
struct wlr_scene_tree *_lp = wl_state.level_trees[_lb]
? wl_state.level_trees[_lb] : &wl_state.scene->tree;
lv->scene_node = wlr_scene_subsurface_tree_create(_lp, ls->surface);
}
/* Compute and send initial configure. */
uint32_t ow = 1920, oh = 1080;
if (ls->output) {
ow = ls->output->width > 0 ? (uint32_t)ls->output->width : 1920;
oh = ls->output->height > 0 ? (uint32_t)ls->output->height : 1080;
}
uint32_t fw, fh;
layer_surface_compute_geometry(ls, ow, oh, &fw, &fh);
wlr_layer_surface_v1_configure(ls, fw, fh);
/* Position the scene node. */
if (lv->scene_node) {
int sx, sy;
layer_surface_compute_position(ls, ow, oh, fw, fh, &sx, &sy);
wlr_scene_node_set_position(&lv->scene_node->node, sx, sy);
}
/* Wire lifecycle listeners. */
lv->map_listener.notify = handle_layer_surface_map;
lv->unmap_listener.notify = handle_layer_surface_unmap;
lv->destroy_listener.notify = handle_layer_surface_destroy;
wl_signal_add(&ls->surface->events.map, &lv->map_listener);
wl_signal_add(&ls->surface->events.unmap, &lv->unmap_listener);
wl_signal_add(&ls->events.destroy, &lv->destroy_listener);
wl_list_insert(&wl_state.layer_list, &lv->link);
wwarning("wl_backend: layer surface new layer=%d %ux%u namespace=%s",
ls->current.layer, fw, fh,
ls->namespace ? ls->namespace : "");
}
/* ------------------------------------------------------------------ */
/* Keyboard grabs */
/* ------------------------------------------------------------------ */
/* Screen internals */
int wl_screen_select_input(WScreen *scr, long mask) { (void)scr; (void)mask; return 1; }
void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; }
void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; }
void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; }
#if USE_XWAYLAND
void
wl_screen_alloc_gcs(WScreen *scr)
{
XGCValues gcv;
int gcm;
static const char stipple_data[] = { 0x02, 0x01 };
/* Pre-initialise menu text colors to black so that setMenuTextColor /
* setMenuDisabledColor in wReadDefaults can safely compare them before
* both have been set from the defaults DB. */
scr->menu_title_color[0] = WMRetainColor(scr->white);
scr->mtext_color = WMCreateRGBColor(scr->wmscreen, 0, 0, 0, True);
scr->dtext_color = WMCreateRGBColor(scr->wmscreen, 0, 0, 0, True);
/* Create X11 GCs on the XWayland connection. wmaker's rendering code
* (framewin, icon, menu) uses these regardless of the display backend.
* Mirrors x11_screen_alloc_gcs exactly; without these, CopyArea and
* XRender calls use GC/drawable 0 → BadDrawable → XWayland broken pipe. */
if (!wl_state.x_display)
return;
/* Mark the wrlib drawable as override_redirect so XWayland's XWM does not
* manage it as a client window. XWM reads override_redirect from the
* CreateNotify event (already gone), but any subsequent ConfigureNotify
* updates the flag on the wlr_xwayland_surface it created. Combined with
* the explicit window-ID filter in handle_xwayland_new_surface, this
* prevents BadDrawable/RenderBadPicture cascade and the resulting broken
* pipe from the nested Wayland backend. */
#if USE_XWAYLAND
if (scr->w_win) {
wl_state.rcontext_xwin = (WNativeWindow)scr->w_win;
XSetWindowAttributes or_attr;
or_attr.override_redirect = True;
XChangeWindowAttributes(wl_state.x_display, (WNativeWindow)scr->w_win, CWOverrideRedirect, &or_attr);
XFlush(wl_state.x_display);
}
#endif
scr->stipple_bitmap = XCreateBitmapFromData(wl_state.x_display, (WNativeWindow)scr->w_win,
stipple_data, 2, 2);
gcv.stipple = scr->stipple_bitmap;
gcv.foreground = scr->white_pixel;
gcv.fill_style = FillStippled;
gcv.graphics_exposures = False;
gcm = GCForeground | GCStipple | GCFillStyle | GCGraphicsExposures;
scr->stipple_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win, gcm, &gcv);
gcv.function = GXcopy;
gcv.foreground = scr->white_pixel;
gcv.background = scr->black_pixel;
gcv.line_width = 1;
gcv.line_style = LineDoubleDash;
gcv.fill_style = FillSolid;
gcv.dash_offset = 0;
gcv.dashes = 4;
gcv.graphics_exposures = False;
gcm = GCFunction | GCGraphicsExposures | GCForeground | GCBackground
| GCLineWidth | GCLineStyle | GCFillStyle | GCDashOffset | GCDashList;
scr->icon_select_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win, gcm, &gcv);
gcv.function = GXxor;
gcv.plane_mask = AllPlanes;
gcv.foreground = scr->white_pixel;
if (gcv.foreground == 0)
gcv.foreground = 1;
gcv.line_width = DEF_FRAME_THICKNESS;
gcv.subwindow_mode = IncludeInferiors;
gcv.graphics_exposures = False;
scr->frame_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->root_win,
GCForeground | GCGraphicsExposures | GCFunction
| GCSubwindowMode | GCLineWidth | GCPlaneMask, &gcv);
gcv.foreground = scr->white_pixel;
if (gcv.foreground == 0)
gcv.foreground = 17;
gcv.function = GXxor;
gcv.subwindow_mode = IncludeInferiors;
gcv.line_width = 1;
gcv.cap_style = CapRound;
gcv.graphics_exposures = False;
gcm = GCForeground | GCFunction | GCSubwindowMode | GCLineWidth
| GCCapStyle | GCGraphicsExposures;
scr->line_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->root_win, gcm, &gcv);
scr->line_pixel = gcv.foreground;
gcv.foreground = scr->white_pixel;
gcv.background = scr->black_pixel;
gcv.graphics_exposures = False;
scr->copy_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win,
GCForeground | GCBackground | GCGraphicsExposures, &gcv);
gcv.graphics_exposures = False;
scr->draw_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win, GCGraphicsExposures, &gcv);
scr->mono_gc = XCreateGC(wl_state.x_display, scr->stipple_bitmap, GCGraphicsExposures, &gcv);
}
#else
void wl_screen_alloc_gcs(WScreen *scr) { (void)scr; }
#endif
void
wl_screen_create_internals(WScreen *scr)
{
wl_state.scr = scr;
{
FILE *fp = fopen("/tmp/wl_backend_trace.log", "a");
if (fp) { fprintf(fp, "wl_screen_create_internals: scr=%p\n", (void*)scr); fclose(fp); }
}
#if USE_XWAYLAND
/* Create off-screen InputOnly X window as focus sink */
if (wl_state.x_display) {
XSetWindowAttributes attribs;
int vmask = CWEventMask | CWOverrideRedirect;
attribs.event_mask = WM_EVMASK_KEY_PRESS | WM_EVMASK_FOCUS_CHANGE;
attribs.override_redirect = True;
WNativeWindow xwin = XCreateWindow(wl_state.x_display, DefaultRootWindow(wl_state.x_display),
-10, -10, 4, 4, 0,
0, InputOnly, CopyFromParent, vmask, &attribs);
if (xwin) {
XSelectInput(wl_state.x_display, xwin, WM_EVMASK_KEY_PRESS | WM_EVMASK_KEY_RELEASE);
XMapWindow(wl_state.x_display, xwin);
/* Do NOT call XSetInputFocus here: the window may not be viewable
* yet in XWayland (map is async), triggering BadMatch that perturbs
* XWayland state and causes a Wayland broken-pipe cascade.
* wl_focus_set() will clear Wayland seat focus via
* wlr_seat_keyboard_clear_focus when this window is the target. */
wl_state.no_focus_xwin = xwin;
scr->no_focus_win = (WNativeWindow)xwin;
}
}
#endif
wPreferences.auto_focus = 1;
if (wl_state.seat)
wlr_seat_keyboard_clear_focus(wl_state.seat);
wSetModifierMasks(WM_MOD_MOD1, WM_MOD_MOD1, WM_MOD_MOD4, 0);
#if USE_XWAYLAND
/* XWayland bridge: create X11 helper window for EWMH */
if (wl_state.x_display) {
XSetWindowAttributes iattr;
iattr.override_redirect = True;
WNativeWindow xwin = XCreateWindow(wl_state.x_display, DefaultRootWindow(wl_state.x_display),
-2, -2, 1, 1, 0,
0, InputOutput, CopyFromParent,
CWOverrideRedirect, &iattr);
if (xwin)
scr->info_window = (WNativeWindow)xwin;
}
#endif
/* Create dock_shadow: scene_buffer used as ghost overlay during icon drag */
if (wl_state.scene) {
int sz = wPreferences.icon_size;
struct wl_frame_buf *dsfb = wmalloc(sizeof(*dsfb));
memset(dsfb, 0, sizeof(*dsfb));
dsfb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, sz, sz, NULL, sz * 4);
if (dsfb->image) {
wlr_buffer_init(&dsfb->base, &frame_buf_impl, sz, sz);
dsfb->id = wl_state.next_synthetic_id++;
dsfb->width = sz;
dsfb->height = sz;
struct wlr_scene_tree *ds_parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
dsfb->tree = wlr_scene_tree_create(ds_parent);
if (dsfb->tree) {
dsfb->scene_buf = wlr_scene_buffer_create(dsfb->tree, &dsfb->base);
if (dsfb->scene_buf)
wlr_scene_node_set_enabled(&dsfb->scene_buf->node, false);
wlr_scene_node_set_enabled(&dsfb->tree->node, false);
}
wl_list_insert(&wl_state.frame_list, &dsfb->link);
scr->dock_shadow = dsfb->id;
} else {
wfree(dsfb);
}
}
/* dock/clip now uses XWayland windows for rendering.
* The WM's dock logic (dock.c) creates X11 windows on the XWayland
* Display* and draws on them with Xlib — the compositor composites
* them like any other XWayland surface. */
/* wPreferences.flags.noclip = 1; */
/* wPreferences.flags.nodock = 1; */
/* dock creation is deferred to wl_event_loop_run() (after the
* startup pump thread stops). wm_dock_create() calls wlr_scene_buffer_create
* which is not thread-safe — calling it here races with the pump thread's
* frame-timer callback that calls wlr_scene_output_commit on the same scene.
* The dock_scr pointer tells wl_event_loop_run() to create the dock once the
* compositor's single-threaded event loop is running.
* NOTE: native dock panel disabled — WM dock uses XWayland windows now. */
/* wl_state.dock_pending_scr = scr; */
/* Wayland has no X11-style window border; force to 0 so policy code
* does not offset client positions by a non-existent border. */
scr->frame_border_width = 0;
/* Create workspace name badge overlay (initially 10x10, resized on use) */
if (wl_state.scene) {
struct wl_frame_buf *fb = wmalloc(sizeof(*fb));
memset(fb, 0, sizeof(*fb));
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, 10, 10, NULL, 0);
if (fb->image) {
wlr_buffer_init(&fb->base, &frame_buf_impl, 10, 10);
fb->id = wl_state.next_synthetic_id++;
fb->width = 10;
fb->height = 10;
struct wlr_scene_tree *parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(parent);
if (fb->tree) {
fb_attach_tree_listener(fb);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf) {
fb_attach_scene_buf_listener(fb);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
wlr_scene_node_set_enabled(&fb->tree->node, false);
}
wl_list_insert(&wl_state.frame_list, &fb->link);
W_RegisterBacking(fb->id, fb->image);
scr->workspace_name = fb->id;
} else {
wfree(fb);
}
}
}
void wl_screen_create_pixmaps(WScreen *scr) { (void)scr; }
void
wl_screen_load_tech_font(WScreen *scr)
{
if (!scr) return;
/* Wayland mode has no native font handle (overlay text is drawn via
* the embedded 5×9 bitmap font in wl_overlay_draw_string). We just
* publish the metric fields so moveres.c gets correct geometry. */
scr->tech_draw_font = NULL;
scr->tech_draw_font_ascent = OVL_GH - 2; /* 7 */
scr->tech_draw_font_descent = 2;
}
void wl_screen_show_mini_screenshot(WScreen *scr, RImage *img) { (void)scr; (void)img; }
RImage *wl_screen_capture_area(WScreen *scr) { (void)scr; return NULL; }
RImage *wl_screen_capture_window(WScreen *scr, WWindow *wwin) { (void)scr; (void)wwin; return NULL; }
void wl_refresh_desktop(WScreen *scr) { (void)scr; }
/* --- Desktop background rendering --- */
/* Simple wlr_buffer implementation backed by a pixman_image_t */
struct wl_bg_buffer {
struct wlr_buffer base;
pixman_image_t *image;
};
static void bg_buffer_destroy(struct wlr_buffer *wlr_buf)
{
struct wl_bg_buffer *buf = wl_container_of(wlr_buf, buf, base);
if (buf->image)
pixman_image_unref(buf->image);
free(buf);
}
static bool bg_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buf,
uint32_t flags, void **data, uint32_t *format, size_t *stride)
{
(void)flags;
struct wl_bg_buffer *buf = wl_container_of(wlr_buf, buf, base);
*data = pixman_image_get_data(buf->image);
*format = DRM_FORMAT_ARGB8888;
*stride = pixman_image_get_stride(buf->image);
return true;
}
static void bg_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buf)
{
(void)wlr_buf;
}
static const struct wlr_buffer_impl bg_buffer_impl = {
.destroy = bg_buffer_destroy,
.begin_data_ptr_access = bg_buffer_begin_data_ptr_access,
.end_data_ptr_access = bg_buffer_end_data_ptr_access,
};
static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out)
{
WMColorRGB wmc;
if (!name || !wGetColor(scr, name, &wmc))
return 0;
out->red = wmc.red >> 8;
out->green = wmc.green >> 8;
out->blue = wmc.blue >> 8;
out->alpha = 255;
return 1;
}
void wl_set_desktop_background(WScreen *scr, const char *texture_desc)
{
if (!scr || !texture_desc)
return;
if (!wl_state.output) {
/* Output not ready yet — cache the background for later */
wfree(wl_state.pending_background);
wl_state.pending_background = wstrdup(texture_desc);
return;
}
int width = wl_state.output->width;
int height = wl_state.output->height;
/* Parse the texture proplist */
WMPropList *texpl = WMCreatePropListFromDescription(texture_desc);
if (!texpl) {
return;
}
/* Render the texture to an RImage using wrlib */
RContext *rc = scr->rcontext;
RImage *bg = NULL;
if (WMIsPLArray(texpl) && WMGetPropListItemCount(texpl) > 0) {
WMPropList *type_pl = WMGetFromPLArray(texpl, 0);
const char *type = type_pl ? WMGetFromPLString(type_pl) : "";
if (strcasecmp(type, "solid") == 0 && WMGetPropListItemCount(texpl) >= 2) {
const char *colorstr = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
RColor color = {0x50, 0x50, 0x75, 0xFF};
if (colorstr)
parse_color_to_rcolor(scr, colorstr, &color);
/* For solid colors, just update the scene rect directly */
if (wl_state.bg_rect) {
float c[4] = { color.red/255.0f, color.green/255.0f, color.blue/255.0f, 1.0f };
wlr_scene_rect_set_color(wl_state.bg_rect, c);
wlr_scene_node_set_enabled(&wl_state.bg_rect->node, true);
if (wl_state.bg_image)
wlr_scene_node_set_enabled(&wl_state.bg_image->node, false);
}
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
WMReleasePropList(texpl);
return;
} else if ((strcasecmp(type, "vgradient") == 0 ||
strcasecmp(type, "hgradient") == 0 ||
strcasecmp(type, "dgradient") == 0) &&
WMGetPropListItemCount(texpl) >= 3) {
const char *c1str = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
const char *c2str = WMGetFromPLString(WMGetFromPLArray(texpl, 2));
RColor color1 = {0, 0, 0, 255}, color2 = {0, 0, 0, 255};
if (c1str) parse_color_to_rcolor(scr, c1str, &color1);
if (c2str) parse_color_to_rcolor(scr, c2str, &color2);
int style = (type[0] == 'v' || type[0] == 'V') ? RVerticalGradient :
(type[0] == 'h' || type[0] == 'H') ? RHorizontalGradient :
RDiagonalGradient;
bg = RRenderGradient(width, height, &color1, &color2, style);
} else if ((strcasecmp(type, "mvgradient") == 0 ||
strcasecmp(type, "mhgradient") == 0 ||
strcasecmp(type, "mdgradient") == 0) &&
WMGetPropListItemCount(texpl) >= 3) {
int ncolors = WMGetPropListItemCount(texpl) - 1;
RColor **colors = wmalloc((ncolors + 1) * sizeof(RColor *));
RColor *cbuf = wmalloc(ncolors * sizeof(RColor));
int ci;
for (ci = 0; ci < ncolors; ci++) {
const char *cs = WMGetFromPLString(WMGetFromPLArray(texpl, ci + 1));
cbuf[ci] = (RColor){0, 0, 0, 255};
if (cs) parse_color_to_rcolor(scr, cs, &cbuf[ci]);
colors[ci] = &cbuf[ci];
}
colors[ncolors] = NULL;
int style = (type[1] == 'v' || type[1] == 'V') ? RVerticalGradient :
(type[1] == 'h' || type[1] == 'H') ? RHorizontalGradient :
RDiagonalGradient;
bg = RRenderMultiGradient(width, height, colors, style);
wfree(cbuf);
wfree(colors);
} else if ((strcasecmp(type, "pixmap") == 0 ||
strcasecmp(type, "spixmap") == 0 ||
strcasecmp(type, "mpixmap") == 0 ||
strcasecmp(type, "cpixmap") == 0 ||
strcasecmp(type, "fpixmap") == 0 ||
strcasecmp(type, "tpixmap") == 0) &&
WMGetPropListItemCount(texpl) >= 2) {
const char *path = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
/* Background color (3rd argument, optional) */
RColor bgcolor = {0, 0, 0, 255};
if (WMGetPropListItemCount(texpl) >= 3) {
const char *cstr = WMGetFromPLString(WMGetFromPLArray(texpl, 2));
if (cstr) parse_color_to_rcolor(scr, cstr, &bgcolor);
}
if (path) {
/* Try the path as-is first, then search pixmap_path */
RImage *img = RLoadImage(rc, path, 0);
if (!img && path[0] != '/') {
/* Relative path — search pixmap_path */
char *search_paths[] = { wPreferences.pixmap_path, PKGDATADIR "/Backgrounds", NULL };
for (int _pi = 0; search_paths[_pi]; _pi++) {
char _full[PATH_MAX];
snprintf(_full, sizeof(_full), "%s/%s", search_paths[_pi], path);
img = RLoadImage(rc, _full, 0);
if (img) break;
}
}
if (!img) {
} else {
/* Combine with background color if image has alpha */
if (img->format == RRGBAFormat)
RCombineImageWithColor(img, &bgcolor);
char mode = (type[0] >= 'a' && type[0] <= 'z') ? type[0] - 32 : type[0];
if (mode == 'S') {
/* Scaled: stretch to exact screen size */
bg = RSmoothScaleImage(img, width, height);
} else if (mode == 'F') {
/* Filled: scale preserving aspect, fill screen (crop) */
int w, h;
if (img->width * height > img->height * width) {
w = (height * img->width) / img->height;
h = height;
} else {
w = width;
h = (width * img->height) / img->width;
}
RImage *scaled = RSmoothScaleImage(img, w, h);
if (scaled) {
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int sx = (scaled->width - width) / 2;
int sy = (scaled->height - height) / 2;
RCopyArea(bg, scaled, sx, sy, width, height, 0, 0);
}
RReleaseImage(scaled);
}
} else if (mode == 'M') {
/* Maximized: scale preserving aspect, fit inside (letterbox) */
int w, h;
if (img->width * height > img->height * width) {
w = width;
h = (width * img->height) / img->width;
} else {
w = (height * img->width) / img->height;
h = height;
}
RImage *scaled = RSmoothScaleImage(img, w, h);
if (scaled) {
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int dx = (width - scaled->width) / 2;
int dy = (height - scaled->height) / 2;
RCombineArea(bg, scaled, 0, 0, scaled->width, scaled->height, dx, dy);
}
RReleaseImage(scaled);
}
} else if (mode == 'C') {
/* Centered: place at center, no scaling */
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int dx = (width - img->width) / 2;
int dy = (height - img->height) / 2;
/* Handle images larger than screen */
int sx = 0, sy = 0, cw = img->width, ch = img->height;
if (dx < 0) { sx = -dx; dx = 0; cw = width; }
if (dy < 0) { sy = -dy; dy = 0; ch = height; }
RCombineArea(bg, img, sx, sy, cw, ch, dx, dy);
}
} else if (mode == 'T') {
/* Tiled: repeat the image */
bg = RCreateImage(width, height, 0);
if (bg) {
for (int ty = 0; ty < height; ty += img->height) {
for (int tx = 0; tx < width; tx += img->width) {
int cw = img->width;
int ch = img->height;
if (tx + cw > width) cw = width - tx;
if (ty + ch > height) ch = height - ty;
RCombineArea(bg, img, 0, 0, cw, ch, tx, ty);
}
}
}
} else {
/* Default (plain "pixmap"): scale to fill */
bg = RSmoothScaleImage(img, width, height);
}
RReleaseImage(img);
}
}
}
}
WMReleasePropList(texpl);
if (!bg) {
return;
}
/* Convert RImage to pixman_image and set as scene buffer */
struct wl_bg_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) { RReleaseImage(bg); return; }
buf->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, width * 4);
if (!buf->image) { free(buf); RReleaseImage(bg); return; }
/* Copy RImage (RGBA or RGB) into ARGB32 pixman buffer */
uint32_t *dst = (uint32_t *)pixman_image_get_data(buf->image);
unsigned char *src = bg->data;
int npixels = width * height;
if (bg->format == RRGBAFormat) {
for (int i = 0; i < npixels; i++) {
dst[i] = ((uint32_t)src[3] << 24) | ((uint32_t)src[0] << 16) |
((uint32_t)src[1] << 8) | (uint32_t)src[2];
src += 4;
}
} else {
for (int i = 0; i < npixels; i++) {
dst[i] = 0xFF000000 | ((uint32_t)src[0] << 16) |
((uint32_t)src[1] << 8) | (uint32_t)src[2];
src += 3;
}
}
RReleaseImage(bg);
wlr_buffer_init(&buf->base, &bg_buffer_impl, width, height);
/* Hide the solid-color rect */
if (wl_state.bg_rect)
wlr_scene_node_set_enabled(&wl_state.bg_rect->node, false);
/* Replace or create the background scene buffer */
if (wl_state.bg_image) {
wlr_scene_buffer_set_buffer(wl_state.bg_image, &buf->base);
wlr_scene_node_set_enabled(&wl_state.bg_image->node, true);
} else {
wl_state.bg_image = wlr_scene_buffer_create(
wl_state.level_trees[WL_LAYER_BACKGROUND], &buf->base);
}
/* Release old buffer, keep new one */
if (wl_state.bg_wlr_buf)
wlr_buffer_drop(wl_state.bg_wlr_buf);
wl_state.bg_wlr_buf = &buf->base;
/* Schedule a frame to display the new background immediately */
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
}
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;
}
WHeadGeometry *heads = wmalloc(sizeof(WHeadGeometry));
if (wl_state.output_layout) {
struct wlr_box box = {0};
wlr_output_layout_get_box(wl_state.output_layout, wl_state.output, &box);
if (box.width > 0 || box.height > 0) {
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;
}
void wl_monitors_select_events(WScreen *scr) { (void)scr; }