1170 lines
50 KiB
C
1170 lines
50 KiB
C
/*
|
|
* 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 wl_protocols_init(void);
|
|
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);
|
|
void handle_seat_request_set_selection(struct wl_listener *listener, void *data);
|
|
void handle_seat_request_start_drag(struct wl_listener *listener, void *data);
|
|
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);
|
|
void handle_idle_new_inhibitor(struct wl_listener *listener, void *data);
|
|
extern void handle_pointer_axis(struct wl_listener *listener, void *data);
|
|
void handle_new_pointer_constraint(struct wl_listener *listener, void *data);
|
|
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);
|
|
extern 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 removed — no persistent X connection. */
|
|
|
|
#if USE_XWAYLAND
|
|
extern int wl_handle_x11_events(int fd, uint32_t mask, void *data);
|
|
#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();
|
|
#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;
|
|
/* Save the host compositor's WAYLAND_DISPLAY before anything
|
|
* overwrites it, so restart (execvp) can restore it and the
|
|
* new instance can find the host compositor. */
|
|
const char *_hwd = getenv("WAYLAND_DISPLAY");
|
|
if (_hwd)
|
|
snprintf(wl_state.host_wayland_display, sizeof(wl_state.host_wayland_display), "%s", _hwd);
|
|
else
|
|
wl_state.host_wayland_display[0] = '\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);
|
|
if (wl_state.output_layout) {
|
|
extern void wl_handle_output_layout_change(struct wl_listener *listener, void *data);
|
|
wl_state.output_layout_change_listener.notify = wl_handle_output_layout_change;
|
|
wl_signal_add(&wl_state.output_layout->events.change,
|
|
&wl_state.output_layout_change_listener);
|
|
}
|
|
|
|
/* 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);
|
|
wwarning("wl_backend: xcursor theme=%s size=%u",
|
|
theme ? theme : "(default)", size);
|
|
}
|
|
if (wl_state.xcursor_mgr) {
|
|
wlr_xcursor_manager_load(wl_state.xcursor_mgr, 1.0f);
|
|
wlr_xcursor_manager_load(wl_state.xcursor_mgr, 2.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();
|
|
if (wl_state.scene && wl_state.output_layout)
|
|
wl_state.scene_output_layout =
|
|
wlr_scene_attach_output_layout(wl_state.scene, wl_state.output_layout);
|
|
/* 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_GetFrameBufImageFn)(unsigned long id);
|
|
extern void *W_GetFrameBufImage(unsigned long id);
|
|
W_GetFrameBufImageFn = W_GetFrameBufImage;
|
|
|
|
extern void (*W_SetCursorImageFn)(const char *name);
|
|
extern void wl_set_cursor_image(const char *name);
|
|
W_SetCursorImageFn = wl_set_cursor_image;
|
|
|
|
extern void (*W_ViewDestroyedFn)(struct W_View *view);
|
|
extern void wl_view_destroyed_hook(struct W_View *v);
|
|
W_ViewDestroyedFn = wl_view_destroyed_hook;
|
|
|
|
extern int (*W_WLPointerGrabFn)(WNativeWindow, WNativeWindow,
|
|
unsigned int, int, int, unsigned long);
|
|
extern void (*W_WLPointerUngrabFn)(unsigned long);
|
|
extern int wl_pointer_grab(WNativeWindow, WNativeWindow,
|
|
unsigned int, int, int, unsigned long);
|
|
extern void wl_pointer_ungrab(unsigned long);
|
|
W_WLPointerGrabFn = wl_pointer_grab;
|
|
|
|
W_WLPointerUngrabFn = wl_pointer_ungrab;
|
|
|
|
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);
|
|
|
|
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);
|
|
wl_list_init(&wl_state.outputs);
|
|
wl_list_init(&wl_state.real_pointer_listeners);
|
|
wl_state.output_count = 0;
|
|
{
|
|
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;
|
|
|
|
/* For nested mode, queue output windows before start; nested wlroots
|
|
* backends don't automatically honor WLR_WL_OUTPUTS for us here, so
|
|
* create one wl_output per requested slot. If the environment variable
|
|
* is a plain integer use that count; otherwise treat any non-empty value
|
|
* as a comma-separated list of output specs and create one output per
|
|
* comma-delimited entry. */
|
|
if (wl_state.is_nested && wl_state.wl_backend_inner) {
|
|
const char *outs_env = getenv("WLR_WL_OUTPUTS");
|
|
int requested = 1;
|
|
|
|
if (outs_env && *outs_env) {
|
|
char *end = NULL;
|
|
long n = strtol(outs_env, &end, 10);
|
|
if (end && *end == '\0' && n > 0) {
|
|
requested = (int)n;
|
|
} else {
|
|
int count = 1;
|
|
for (const char *p = outs_env; *p; ++p) {
|
|
if (*p == ',')
|
|
count++;
|
|
}
|
|
requested = count;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < requested; i++)
|
|
wlr_wl_output_create(wl_state.wl_backend_inner);
|
|
}
|
|
|
|
/* Create XKB keymap BEFORE starting the backend so that keyboards
|
|
* detected during wlr_backend_start() (libinput fires new_input
|
|
* synchronously) can have the keymap applied immediately. */
|
|
{
|
|
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: XKB keymap created (layout=%s variant=%s)",
|
|
rules.layout ? rules.layout : "(default)",
|
|
rules.variant ? rules.variant : "(none)");
|
|
else
|
|
wwarning("wl_backend: XKB keymap creation failed");
|
|
}
|
|
}
|
|
|
|
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_protocols_init();
|
|
|
|
/* 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_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);
|
|
|
|
extern void handle_xdg_new_popup(struct wl_listener *listener, void *data);
|
|
wl_state.xdg_new_popup_listener.notify = handle_xdg_new_popup;
|
|
wl_signal_add(&wl_state.xdg_shell->events.new_popup,
|
|
&wl_state.xdg_new_popup_listener);
|
|
|
|
#ifdef USE_XWAYLAND
|
|
/* Create XWayland server for X11 client support */
|
|
wl_state.xwayland = wlr_xwayland_create(wl_state.display,
|
|
wl_state.compositor, false);
|
|
#else
|
|
wl_state.xwayland = NULL;
|
|
#endif
|
|
#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.
|
|
* 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. */
|
|
#ifdef USE_XWAYLAND
|
|
if (wl_state.xwayland) {
|
|
wwarning("wl_backend: XWayland configured (eager mode) — ready at startup");
|
|
wl_state.xwayland_ready = true;
|
|
}
|
|
#endif
|
|
|
|
return NULL; /* dpy=NULL so WM core skips Xlib calls */
|
|
}
|
|
|
|
void
|
|
wl_display_close(void)
|
|
{
|
|
if (!wl_state.started)
|
|
return;
|
|
|
|
/* X11 event source and Xlib connection removed — wlroots manages
|
|
* XWayland internally. */
|
|
#if USE_XWAYLAND
|
|
|
|
/* 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");
|
|
}
|
|
#endif
|
|
/* 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.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.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.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;
|
|
}
|
|
|
|
/* Clean up the Wayland socket so a new compositor instance can bind.
|
|
* wl_display_destroy() would do this, but in wlroots 0.19 it aborts
|
|
* because the multi-backend's display-destroy handler iterates freed
|
|
* child backend pointers. Since execvp replaces the entire process
|
|
* image, all memory and fd resources are reclaimed by the kernel.
|
|
* The socket file on disk is the only persistent resource we need
|
|
* to remove ourselves. */
|
|
if (wl_state.socket_name[0]) {
|
|
const char *rt = getenv("XDG_RUNTIME_DIR");
|
|
if (rt) {
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "%s/%s", rt, wl_state.socket_name);
|
|
unlink(path);
|
|
}
|
|
}
|
|
if (wl_state.host_wayland_display[0])
|
|
setenv("WAYLAND_DISPLAY", wl_state.host_wayland_display, 1);
|
|
else
|
|
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;
|
|
/* Clean up per-output state */
|
|
{
|
|
struct wl_wlr_output_state *ost, *tmp;
|
|
wl_list_for_each_safe(ost, tmp, &wl_state.outputs, link) {
|
|
wl_list_remove(&ost->link);
|
|
wfree(ost);
|
|
}
|
|
}
|
|
wl_state.output_count = 0;
|
|
wl_state.output = NULL;
|
|
wl_state.frame_timer = NULL;
|
|
wl_state.output_layout = 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. */
|
|
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, -1); /* block until fd ready */
|
|
|
|
/* 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");
|
|
|
|
}
|
|
|
|
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
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Keyboard grabs */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|