diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index ee541c4..3969356 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -65,6 +65,24 @@ * receive synthetic WNativeWindow IDs equal to their xcb_window_t, and * are managed through the same wl_toplevel_view / wManageWindow lifecycle * as XDG toplevels. DISPLAY env var is set so X11 clients can connect. + * Phase 37: zwlr_data_control_manager_v1, zwlr_input_inhibitor_v1, + * zwlr_virtual_pointer_manager_v1, org_kde_kwin_idle, zxdg_exporter/importer_v2 + * advertised. Input inhibitor activate/deactivate wired. + * Phase 36: zwlr_layer_shell_v1 advertised; new_surface handler creates a + * wl_layer_view, assigns output, computes anchor/margin geometry, sends + * configure, and positions the scene node. Map/unmap/destroy wired. + * Phase 35: zwlr_foreign_toplevel_management_v1 advertised; handle created + * on map, destroyed on unmap, with title/app_id/output/activated state set. + * xdg_activation_v1 advertised; request_activate delivers WM focus + raise. + * Phase 34: zwp_text_input_manager_v3, zwp_input_method_manager_v2, and + * zwp_virtual_keyboard_manager_v1 advertised for IME / on-screen keyboard. + * Phase 33: zwlr_output_management_v1, zwlr_output_power_management_v1, + * zwlr_gamma_control_manager_v1, and wp_presentation advertised. + * Output management apply/test requests acknowledged (single-output no-op). + * Output power set_mode applies DPMS on/off via wlr_output_enable/commit. + * Phase 32: zwp_linux_dmabuf_v1, zwlr_screencopy_manager_v1, and + * zwlr_export_dmabuf_manager_v1 advertised as passive globals so GPU + * zero-copy buffers, screenshot tools, and screen recorders work. * Phase 18: nested Wayland compositor + wlr_scene rendering. * If WMAKER_WAYLAND_NESTED=1 is set, a wlr_wl_backend is used alongside the * headless backend (via wlr_multi_backend) so the compositor appears as a @@ -118,6 +136,35 @@ #include /* Phase 26: zwp_primary_selection_device_manager_v1 */ #include /* Phase 27: xdg-output for panel geometry */ #include /* Phase 28: idle inhibit for video/fullscreen */ +#include /* Phase 29: wp_viewporter for video scaling */ +#include /* Phase 30: relative pointer for games */ +/* Generated protocol header required by wlr_pointer_constraints_v1.h */ +#include "protocols/pointer-constraints-unstable-v1-protocol.h" +#include /* Phase 30: pointer lock for games */ +#include /* Phase 31: shortcut inhibit for games */ +#include /* Phase 32: zwp_linux_dmabuf_v1 for GPU zero-copy */ +#include /* Phase 32: zwlr_screencopy_manager_v1 for screenshots */ +#include /* Phase 32: zwlr_export_dmabuf_manager_v1 for screen recorders */ +#include /* Phase 33: zwlr_output_management_v1 (randr equivalent) */ +/* Generated protocol header required by wlr_output_power_management_v1.h */ +#include "protocols/wlr-output-power-management-unstable-v1-protocol.h" +#include /* Phase 33: zwlr_output_power_management_v1 (DPMS) */ +#include /* Phase 33: zwlr_gamma_control_manager_v1 (Night Light) */ +#include /* Phase 33: wp_presentation (smooth frame timing) */ +#include /* Phase 34: zwp_text_input_manager_v3 (IME client) */ +#include /* Phase 34: zwp_input_method_manager_v2 (IME server) */ +#include /* Phase 34: zwp_virtual_keyboard_manager_v1 */ +#include /* Phase 35: zwlr_foreign_toplevel_management_v1 */ +#include /* Phase 35: xdg_activation_v1 (focus tokens) */ +/* Generated protocol header required by wlr_layer_shell_v1.h */ +#include "protocols/wlr-layer-shell-unstable-v1-protocol.h" +#include /* Phase 36: zwlr_layer_shell_v1 (panels, docks) */ +#include /* Phase 37: zwlr_data_control_manager_v1 (clipboard mgr) */ +#include /* Phase 37: zwlr_input_inhibitor_v1 (lock screen input) */ +#include /* Phase 37: zwlr_virtual_pointer_manager_v1 (remote desktop) */ +#include /* Phase 37: org_kde_kwin_idle (legacy idle protocol) */ +#include /* Phase 37: xdg-foreign registry (shared state) */ +#include /* Phase 37: zxdg_exporter_v2 / zxdg_importer_v2 */ #include /* Generated xdg-shell protocol implementation (Wayland glue code) */ #include "protocols/xdg-shell-protocol.c" @@ -159,6 +206,8 @@ struct wl_toplevel_view { struct wl_listener set_app_id_listener; /* Phase 16 */ /* Phase 17: XWayland — set for X11 windows, NULL for XDG toplevels. */ struct wlr_xwayland_surface *xw_surface; + /* Path 1: geometry listener — keeps scene node in sync when X11 window moves. */ + struct wl_listener xw_geometry_listener; /* Phase 18: scene graph node for this surface (NULL if scene unavailable). */ struct wlr_scene_node *scene_node; WNativeWindow id; /* synthetic handle, >= 0x10000000 */ @@ -167,9 +216,24 @@ struct wl_toplevel_view { int wm_state_n; /* Phase 56: element count */ char **wm_command; /* Phase 57: WM_COMMAND argv or NULL */ int wm_command_n; /* Phase 57: argument count */ + /* Phase 35: foreign toplevel handle — exposes this window to taskbars. */ + struct wlr_foreign_toplevel_handle_v1 *ft_handle; struct wl_list link; /* wl_state.toplevel_list */ }; +/* + * Phase 36: wl_layer_view — one entry per mapped zwlr_layer_shell_v1 surface. + * Tracks the wlroots layer surface, its scene node, and lifecycle listeners. + */ +struct wl_layer_view { + struct wlr_layer_surface_v1 *layer_surface; + struct wlr_scene_node *scene_node; + struct wl_listener map_listener; + struct wl_listener unmap_listener; + struct wl_listener destroy_listener; + struct wl_list link; /* wl_state.layer_list */ +}; + /* * Context store: three per-key linked lists keyed by WNativeWindow. * Backed by a flat list of (win, data) pairs — fine for the small @@ -206,6 +270,7 @@ struct wl_compositor_state { struct wl_event_source *frame_timer; /* Phase 38: per-toplevel view list and context store */ struct wl_list toplevel_list; /* wl_toplevel_view.link */ + struct wl_list layer_list; /* wl_layer_view.link */ struct wl_list ctx_lists[3]; /* one per WContextKey */ unsigned long next_synthetic_id; /* >= 0x10000000 */ /* Phase 43: headless keyboard device for seat keyboard focus */ @@ -224,6 +289,8 @@ struct wl_compositor_state { /* Phase 17: XWayland server for X11 client support */ struct wlr_xwayland *xwayland; struct wl_listener xwayland_new_surface_listener; + struct wl_listener xwayland_ready_listener; + struct wl_listener xwayland_server_destroy_listener; /* Phase 20: real input devices from nested backend + their listeners */ struct wlr_input_device *real_keyboard_dev; struct wlr_input_device *real_pointer_dev; @@ -275,6 +342,54 @@ struct wl_compositor_state { /* Phase 28: idle-inhibit manager for video players / fullscreen apps. */ struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; struct wl_listener idle_new_inhibitor_listener; + /* Phase 29: wp_viewporter for hardware video scaling / subsurface damage. */ + struct wlr_viewporter *viewporter; + /* Phase 30: relative pointer + pointer constraints for games. */ + struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; + struct wlr_pointer_constraints_v1 *pointer_constraints; + struct wl_listener new_constraint_listener; + /* Phase 31: keyboard shortcuts inhibitor for fullscreen / remote desktop. */ + struct wlr_keyboard_shortcuts_inhibit_manager_v1 *shortcuts_inhibit_mgr; + struct wl_listener new_shortcuts_inhibitor_listener; + /* Phase 32: GPU zero-copy buffer import, screencopy, GPU framebuffer export. */ + struct wlr_linux_dmabuf_v1 *linux_dmabuf; + struct wlr_screencopy_manager_v1 *screencopy_mgr; + struct wlr_export_dmabuf_manager_v1 *export_dmabuf_mgr; + /* Phase 33: output management (randr), output power (DPMS), gamma control, presentation time. */ + struct wlr_output_manager_v1 *output_mgr; + struct wl_listener output_mgr_apply_listener; + struct wl_listener output_mgr_test_listener; + struct wlr_output_power_manager_v1 *output_power_mgr; + struct wl_listener output_power_set_mode_listener; + struct wlr_gamma_control_manager_v1 *gamma_control_mgr; + struct wlr_presentation *presentation; + /* Phase 34: text input (IME client side), input method (IME server side), virtual keyboard. */ + struct wlr_text_input_manager_v3 *text_input_mgr; + struct wl_listener text_input_new_listener; + struct wlr_input_method_manager_v2 *input_method_mgr; + struct wl_listener input_method_new_listener; + struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; + struct wl_listener virtual_keyboard_new_listener; + /* Phase 35: foreign toplevel management + xdg_activation (focus tokens). */ + struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_mgr; + struct wlr_xdg_activation_v1 *xdg_activation; + struct wl_listener xdg_activation_request_listener; + /* Phase 36: layer shell — panels, docks, wallpapers, overlays. */ + struct wlr_layer_shell_v1 *layer_shell; + struct wl_listener layer_shell_new_surface_listener; + /* Phase 37: clipboard manager, input inhibitor, virtual pointer, idle, xdg-foreign. */ + struct wlr_data_control_manager_v1 *data_control_mgr; + struct wlr_input_inhibit_manager *input_inhibit_mgr; + struct wl_listener input_inhibit_activate_listener; + struct wl_listener input_inhibit_deactivate_listener; + struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; + struct wl_listener virtual_pointer_new_listener; + struct wlr_idle *idle; + struct wlr_xdg_foreign_registry *xdg_foreign_registry; + struct wlr_xdg_foreign_v2 *xdg_foreign_v2; + /* Phase 29 axis listeners (headless pointer + real nested pointer). */ + struct wl_listener pointer_axis_listener; + struct wl_listener real_pointer_axis_listener; }; static struct wl_compositor_state wl_state; @@ -341,6 +456,28 @@ static void handle_seat_request_set_selection(struct wl_listener *listener, void static void handle_seat_request_primary_selection(struct wl_listener *listener, void *data); /* Phase 28: idle inhibitor created/destroyed handler. */ static void handle_idle_new_inhibitor(struct wl_listener *listener, void *data); +/* Phase 29: scroll-wheel axis event handler. */ +static void handle_pointer_axis(struct wl_listener *listener, void *data); +/* Phase 30: new pointer constraint handler. */ +static void handle_new_pointer_constraint(struct wl_listener *listener, void *data); +/* Phase 31: new keyboard shortcuts inhibitor handler. */ +static void handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data); +/* Phase 33: output management apply/test + output power set_mode handlers. */ +static void handle_output_mgr_apply(struct wl_listener *listener, void *data); +static void handle_output_mgr_test(struct wl_listener *listener, void *data); +static void handle_output_power_set_mode(struct wl_listener *listener, void *data); +/* Phase 34: text input, input method, virtual keyboard new-instance handlers. */ +static void handle_text_input_new(struct wl_listener *listener, void *data); +static void handle_input_method_new(struct wl_listener *listener, void *data); +static void handle_virtual_keyboard_new(struct wl_listener *listener, void *data); +/* Phase 35: xdg_activation request_activate handler. */ +static void handle_xdg_activation_request(struct wl_listener *listener, void *data); +/* Phase 36: layer shell new_surface handler. */ +static void handle_layer_shell_new_surface(struct wl_listener *listener, void *data); +/* Phase 37: input inhibitor activate/deactivate + virtual pointer handlers. */ +static void handle_input_inhibit_activate(struct wl_listener *listener, void *data); +static void handle_input_inhibit_deactivate(struct wl_listener *listener, void *data); +static void handle_virtual_pointer_new(struct wl_listener *listener, void *data); /* Phase 17: XWayland new surface forward declaration. */ static void handle_xwayland_new_surface(struct wl_listener *listener, void *data); /* Phase 20: nested backend real input device forward declaration. */ @@ -729,6 +866,25 @@ handle_toplevel_map(struct wl_listener *listener, void *data) int cy = view->y + wwin->client.height / 2; wl_pointer_warp_to(cx, cy); } + + /* Phase 35: create a foreign toplevel handle so taskbars + * and docks (e.g. wlr-taskbar, waybar) can enumerate and + * control this window. */ + if (wl_state.foreign_toplevel_mgr && !view->ft_handle) { + view->ft_handle = + wlr_foreign_toplevel_handle_v1_create( + wl_state.foreign_toplevel_mgr); + if (view->ft_handle) { + const char *title = wwin->wm_class ? wwin->wm_class : ""; + const char *app_id = wwin->wm_instance ? wwin->wm_instance : ""; + wlr_foreign_toplevel_handle_v1_set_title(view->ft_handle, title); + wlr_foreign_toplevel_handle_v1_set_app_id(view->ft_handle, app_id); + if (wl_state.output) + wlr_foreign_toplevel_handle_v1_output_enter( + view->ft_handle, wl_state.output); + wlr_foreign_toplevel_handle_v1_set_activated(view->ft_handle, true); + } + } } } } @@ -745,6 +901,13 @@ handle_toplevel_unmap(struct wl_listener *listener, void *data) wwarning("wl_backend: toplevel unmapped id=0x%lx", view->id); + /* Phase 35: destroy the foreign toplevel handle so taskbars stop + * showing this window in their list. */ + if (view->ft_handle) { + wlr_foreign_toplevel_handle_v1_destroy(view->ft_handle); + view->ft_handle = NULL; + } + /* Phase 40: remove the WWindow from the WM's managed list. */ WScreen *scr = wScreenWithNumber(0); if (scr) { @@ -816,8 +979,23 @@ handle_new_toplevel_decoration(struct wl_listener *listener, void *data) /* ------------------------------------------------------------------ */ /* - * handle_xwayland_map — X11 window is ready to be managed. - * Uses the same wManageWindow path as XDG toplevels. + * Path 1: handle_xwayland_set_geometry — X11 window moved/resized. + * Keeps the scene node in sync with the X11-authoritative position. + */ +static void +handle_xwayland_set_geometry(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = + wl_container_of(listener, view, xw_geometry_listener); + struct wlr_xwayland_surface *xw = view->xw_surface; + (void)data; + if (view->scene_node) + wlr_scene_node_set_position(view->scene_node, xw->x, xw->y); +} + +/* + * handle_xwayland_map — X11 window is ready to be shown. + * Add to the scene graph and manage as a WM client via wManageWindow. */ static void handle_xwayland_map(struct wl_listener *listener, void *data) @@ -831,6 +1009,16 @@ handle_xwayland_map(struct wl_listener *listener, void *data) xw->class ? xw->class : "(none)", xw->title ? xw->title : "(none)"); + /* Add to scene graph for compositing regardless of mode. */ + if (wl_state.scene && !view->scene_node && xw->surface) { + view->scene_node = wlr_scene_subsurface_tree_create( + &wl_state.scene->node, xw->surface); + if (view->scene_node) + wlr_scene_node_set_position(view->scene_node, xw->x, xw->y); + } + + + /* Pure Wayland mode: manage as WM client. */ WScreen *scr = wScreenWithNumber(0); if (scr) { WWindow *wwin = wManageWindow(scr, view->id); @@ -841,14 +1029,6 @@ handle_xwayland_map(struct wl_listener *listener, void *data) wwin->wm_class ? wwin->wm_class : "(none)", wwin->wm_instance ? wwin->wm_instance : "(none)", wwin->client.width, wwin->client.height, (int)xw->pid); - /* Phase 18: add XWayland surface to scene graph. */ - if (wl_state.scene && !view->scene_node && xw->surface) { - view->scene_node = wlr_scene_subsurface_tree_create( - &wl_state.scene->node, xw->surface); - if (view->scene_node) - wlr_scene_node_set_position(view->scene_node, - view->x, view->y); - } int cx = view->x + wwin->client.width / 2; int cy = view->y + wwin->client.height / 2; wl_pointer_warp_to(cx, cy); @@ -857,7 +1037,9 @@ handle_xwayland_map(struct wl_listener *listener, void *data) } /* - * handle_xwayland_unmap — X11 window hidden; unmanage it. + * handle_xwayland_unmap — X11 window hidden. + * Path 1: just remove the scene node; WindowMaker handles unmanagement. + * Pure Wayland: unmanage via wUnmanageWindow. */ static void handle_xwayland_unmap(struct wl_listener *listener, void *data) @@ -867,6 +1049,11 @@ handle_xwayland_unmap(struct wl_listener *listener, void *data) wwarning("wl_backend: xwayland unmapped id=0x%lx", view->id); + if (view->scene_node) { + wlr_scene_node_destroy(view->scene_node); + view->scene_node = NULL; + } + WScreen *scr = wScreenWithNumber(0); if (scr) { WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); @@ -890,8 +1077,10 @@ handle_xwayland_destroy(struct wl_listener *listener, void *data) wl_list_remove(&view->unmap_listener.link); wl_list_remove(&view->destroy_listener.link); wl_list_remove(&view->title_listener.link); + if (view->xw_geometry_listener.link.next) + wl_list_remove(&view->xw_geometry_listener.link); wl_list_remove(&view->link); - /* Phase 18: remove scene node. */ + /* Phase 18: remove scene node (may already be gone from unmap). */ if (view->scene_node) { wlr_scene_node_destroy(view->scene_node); view->scene_node = NULL; @@ -927,6 +1116,36 @@ handle_xwayland_set_class(struct wl_listener *listener, void *data) wwin->wm_instance = xw->instance ? strdup(xw->instance) : NULL; } +/* + * Path 1: handle_xwayland_server_destroy — fired when the XWayland server + * process exits before or without firing the ready signal (startup failure). + * Set the failure flag so the pump loop can exit early and clean up. + */ +static void +handle_xwayland_server_destroy(struct wl_listener *listener, void *data) +{ + (void)listener; + (void)data; + wwarning("wl_backend: XWayland server exited"); +} + +static void +handle_xwayland_ready(struct wl_listener *listener, void *data) +{ + (void)listener; + (void)data; + + if (!wl_state.xwayland || !wl_state.xwayland->display_name) { + wwarning("wl_backend: XWayland ready but display_name unavailable"); + return; + } + + /* Advertise the XWayland display so X11 client apps can connect. */ + setenv("DISPLAY", wl_state.xwayland->display_name, 1); + wwarning("wl_backend: XWayland ready on %s (X11 clients can now connect)", + wl_state.xwayland->display_name); +} + /* * handle_xwayland_new_surface — called when a new X11 window connects. * Allocates a wl_toplevel_view, uses window_id as the WNativeWindow ID, @@ -938,9 +1157,10 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data) struct wlr_xwayland_surface *xw = data; (void)listener; - /* Skip override-redirect windows (menus, tooltips — not WM-managed). */ + /* Skip override_redirect windows (menus, tooltips) — we only manage + * top-level client windows. */ if (xw->override_redirect) { - wwarning("wl_backend: xwayland override_redirect window 0x%x (skipped)", + wwarning("wl_backend: xwayland override_redirect 0x%x skipped", (unsigned)xw->window_id); return; } @@ -950,29 +1170,31 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data) view->xw_surface = xw; view->id = (WNativeWindow)xw->window_id; - view->map_listener.notify = handle_xwayland_map; - view->unmap_listener.notify = handle_xwayland_unmap; - view->destroy_listener.notify = handle_xwayland_destroy; - view->title_listener.notify = handle_xwayland_set_class; + view->map_listener.notify = handle_xwayland_map; + view->unmap_listener.notify = handle_xwayland_unmap; + view->destroy_listener.notify = handle_xwayland_destroy; + view->title_listener.notify = handle_xwayland_set_class; + view->xw_geometry_listener.notify = handle_xwayland_set_geometry; - wl_signal_add(&xw->events.map, &view->map_listener); - wl_signal_add(&xw->events.unmap, &view->unmap_listener); - wl_signal_add(&xw->events.destroy, &view->destroy_listener); - wl_signal_add(&xw->events.set_class, &view->title_listener); + wl_signal_add(&xw->events.map, &view->map_listener); + wl_signal_add(&xw->events.unmap, &view->unmap_listener); + wl_signal_add(&xw->events.destroy, &view->destroy_listener); + wl_signal_add(&xw->events.set_class, &view->title_listener); + wl_signal_add(&xw->events.set_geometry, &view->xw_geometry_listener); wl_list_insert(&wl_state.toplevel_list, &view->link); wl_state.managed_surface_count++; - /* Position at the centre of the headless output initially. */ view->x = (wl_state.output ? (int)wl_state.output->width : 1920) / 4; view->y = (wl_state.output ? (int)wl_state.output->height : 1080) / 4; - wwarning("wl_backend: new XWayland surface id=0x%lx class=%s title=%s", + wwarning("wl_backend: new XWayland surface id=0x%lx class=%s title=%s pos=%d,%d", view->id, xw->class ? xw->class : "(none)", - xw->title ? xw->title : "(none)"); + xw->title ? xw->title : "(none)", + xw->x, xw->y); - /* Send an initial configure so the X11 client gets a position/size. */ + /* Send initial configure so the X11 client gets a position. */ wlr_xwayland_surface_configure(xw, (int16_t)view->x, (int16_t)view->y, xw->width > 0 ? xw->width : 640, @@ -1155,6 +1377,10 @@ handle_backend_new_input(struct wl_listener *listener, void *data) wl_state.real_pointer_frame_listener.notify = handle_pointer_frame; wl_signal_add(&dev->pointer->events.frame, &wl_state.real_pointer_frame_listener); + /* Phase 29: axis (scroll wheel) from real nested pointer. */ + wl_state.real_pointer_axis_listener.notify = handle_pointer_axis; + wl_signal_add(&dev->pointer->events.axis, + &wl_state.real_pointer_axis_listener); wwarning("wl_backend: real pointer wired from nested backend"); break; @@ -1513,6 +1739,182 @@ wl_display_open(const char *name) &wl_state.idle_new_inhibitor_listener); wwarning("wl_backend: idle-inhibit manager created"); } + + /* Phase 29: 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"); + + /* Phase 30: relative pointer + pointer constraints for games / pointer lock. */ + 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"); + } + + /* Phase 31: 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"); + } + /* Phase 32: 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(wl_state.display, 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)"); + + /* Phase 33: 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); + if (wl_state.presentation) + wwarning("wl_backend: presentation-time created (wp_presentation)"); + + /* Phase 34: 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)"); + } + + /* Phase 35: 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)"); + } + + /* Phase 36: layer shell — panels, docks, wallpapers, lock screens. */ + wl_state.layer_shell = wlr_layer_shell_v1_create(wl_state.display); + 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)"); + } + + /* Phase 37: 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)"); + + wl_state.input_inhibit_mgr = + wlr_input_inhibit_manager_create(wl_state.display); + if (wl_state.input_inhibit_mgr) { + wl_state.input_inhibit_activate_listener.notify = + handle_input_inhibit_activate; + wl_signal_add(&wl_state.input_inhibit_mgr->events.activate, + &wl_state.input_inhibit_activate_listener); + wl_state.input_inhibit_deactivate_listener.notify = + handle_input_inhibit_deactivate; + wl_signal_add(&wl_state.input_inhibit_mgr->events.deactivate, + &wl_state.input_inhibit_deactivate_listener); + wwarning("wl_backend: input-inhibitor created (zwlr_input_inhibitor_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)"); + } + + wl_state.idle = wlr_idle_create(wl_state.display); + if (wl_state.idle) + wwarning("wl_backend: idle created (org_kde_kwin_idle)"); + + 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); /* Phase 64: advertise zxdg_decoration_manager_v1 so clients that @@ -1588,6 +1990,9 @@ wl_display_open(const char *name) wl_signal_add(&ptr->events.button, &wl_state.pointer_button_listener); wl_state.pointer_frame_listener.notify = handle_pointer_frame; wl_signal_add(&ptr->events.frame, &wl_state.pointer_frame_listener); + /* Phase 29: axis (scroll wheel) listener. */ + wl_state.pointer_axis_listener.notify = handle_pointer_axis; + wl_signal_add(&ptr->events.axis, &wl_state.pointer_axis_listener); wwarning("wl_backend: pointer motion/button/frame listeners wired"); } @@ -1600,6 +2005,7 @@ wl_display_open(const char *name) /* Phase 38: initialise per-toplevel tracking and context store. */ wl_list_init(&wl_state.toplevel_list); + wl_list_init(&wl_state.layer_list); { int ki; for (ki = 0; ki < 3; ki++) @@ -1613,24 +2019,37 @@ wl_display_open(const char *name) wl_signal_add(&wl_state.xdg_shell->events.new_surface, &wl_state.xdg_new_surface_listener); - /* Phase 17: register XWayland for X11 client support. - * Using lazy=true: the Xwayland process does NOT start at compositor - * boot. It starts automatically on the first X11 client connect. - * This avoids a startup race where Xwayland's Wayland client tries to - * negotiate globals before the compositor's event loop is running, - * causing "Broken pipe" and "xcb connect failed" on the first frame. - * The display number is pre-allocated immediately so DISPLAY can be - * set for child processes even before any X11 client connects. */ + /* 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, true); + wl_state.compositor, false); if (wl_state.xwayland) { + /* Wire server destroy signal to detect early failure (e.g. stale + * display lock → Xwayland exits before ready fires). */ + 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); + } + /* Wire ready signal BEFORE new_surface so Display* is set before + * any X11 window events arrive. */ + 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); + /* display_name may already be set (display number reserved by wlroots + * synchronously); set DISPLAY early as a best-effort so that any + * code between here and the ready signal can still find the socket. */ if (wl_state.xwayland->display_name) { setenv("DISPLAY", wl_state.xwayland->display_name, 1); - wwarning("wl_backend: XWayland DISPLAY=%s (lazy: starts on first X11 client)", + wwarning("wl_backend: XWayland starting DISPLAY=%s (eager mode)", wl_state.xwayland->display_name); } else { wwarning("wl_backend: XWayland created (display_name pending)"); @@ -1687,7 +2106,13 @@ wl_display_open(const char *name) wlr_seat_set_keyboard(wl_state.seat, wl_state.real_keyboard_dev); wl_state.started = 1; - return NULL; /* no X11 Display* in the Wayland path */ + + /* XWayland is started but we do not block on its ready signal here. + * The ready callback (handle_xwayland_ready) will set DISPLAY when it + * fires so X11 client apps can connect. The WM's own UI renders via + * native Wayland surfaces — no X11 Display* needed by the WM itself. */ + + return NULL; /* pure Wayland: no X11 Display* used by the WM */ } static void @@ -1746,6 +2171,17 @@ wl_display_close(void) 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"); @@ -1783,10 +2219,12 @@ wl_display_close(void) /* XDG shell and decoration listeners */ WL_LISTENER_REMOVE(wl_state.xdg_new_surface_listener); WL_LISTENER_REMOVE(wl_state.decoration_new_listener); - /* XWayland new-surface listener (xwayland ptr already nulled in step 1, - * but the listener link is still inserted in the (now-orphaned) xwayland - * object's signal list — remove it to prevent heap corruption). */ + /* 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); @@ -1808,6 +2246,28 @@ wl_display_close(void) /* Phase 26/28: primary selection + idle inhibitor listeners */ WL_LISTENER_REMOVE(wl_state.seat_request_primary_selection_listener); WL_LISTENER_REMOVE(wl_state.idle_new_inhibitor_listener); + /* Phase 29: axis listeners */ + WL_LISTENER_REMOVE(wl_state.pointer_axis_listener); + WL_LISTENER_REMOVE(wl_state.real_pointer_axis_listener); + /* Phase 30/31: pointer constraints + shortcuts inhibitor listeners */ + WL_LISTENER_REMOVE(wl_state.new_constraint_listener); + WL_LISTENER_REMOVE(wl_state.new_shortcuts_inhibitor_listener); + /* Phase 33: output management + output power listeners */ + 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); + /* Phase 34: text input, input method, virtual keyboard listeners */ + 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); + /* Phase 35: xdg_activation listener */ + WL_LISTENER_REMOVE(wl_state.xdg_activation_request_listener); + /* Phase 36: layer shell new_surface listener */ + WL_LISTENER_REMOVE(wl_state.layer_shell_new_surface_listener); + /* Phase 37: input inhibitor + virtual pointer listeners */ + WL_LISTENER_REMOVE(wl_state.input_inhibit_activate_listener); + WL_LISTENER_REMOVE(wl_state.input_inhibit_deactivate_listener); + WL_LISTENER_REMOVE(wl_state.virtual_pointer_new_listener); #undef WL_LISTENER_REMOVE /* Phase 23: destroy cursor and xcursor manager before display teardown. */ @@ -1878,8 +2338,10 @@ wl_atom_intern(const char *name) static void wl_event_loop_run(void) { - if (wl_state.display) + if (wl_state.display) { + wwarning("wl_backend: event loop running"); wl_display_run(wl_state.display); + } } static void @@ -1912,7 +2374,7 @@ wl_screen_open(WScreen *scr) scr->scr_height = wl_state.output ? (int)wl_state.output->height : 1080; scr->depth = 24; - scr->root_win = WNATIVE_NONE; + scr->root_win = WNATIVE_NONE; scr->colormap = 0; } @@ -2175,6 +2637,419 @@ handle_idle_new_inhibitor(struct wl_listener *listener, void *data) wwarning("wl_backend: idle inhibitor created (client suppressing screen blank)"); } +/* + * Phase 29 (fix): 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. + */ +static void +handle_pointer_axis(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_event_pointer_axis *ev = data; + if (!wl_state.seat) + return; + /* Drop axis events during a compositor grab so the grabbed window + * does not receive unexpected scroll events mid-move/resize. */ + if (wl_state.grab_type) + return; + wlr_seat_pointer_notify_axis(wl_state.seat, + ev->time_msec, + ev->orientation, + ev->delta, + ev->delta_discrete, + ev->source); +} + +/* + * Phase 30: 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"); +} + +/* + * Phase 31: 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"); +} + +/* ------------------------------------------------------------------ */ +/* Phase 33: output management, output power, gamma control */ +/* ------------------------------------------------------------------ */ + +/* + * handle_output_mgr_apply — client requests a randr-like output reconfiguration. + * For a single-output headless compositor there is nothing to reconfigure; just + * acknowledge success so the client does not hang waiting for feedback. + * The configuration object is compositor-owned and must be destroyed here. + */ +static void +handle_output_mgr_apply(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_output_configuration_v1 *config = data; + wlr_output_configuration_v1_send_succeeded(config); + wlr_output_configuration_v1_destroy(config); + wwarning("wl_backend: output manager apply request — acknowledged (no-op)"); +} + +/* + * handle_output_mgr_test — client tests a configuration without applying it. + * Always report success (any single-output config is trivially valid). + */ +static void +handle_output_mgr_test(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_output_configuration_v1 *config = data; + wlr_output_configuration_v1_send_succeeded(config); + wlr_output_configuration_v1_destroy(config); +} + +/* + * handle_output_power_set_mode — client requests DPMS on/off. + * Apply the requested power state via wlr_output_enable + wlr_output_commit. + */ +static void +handle_output_power_set_mode(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_output_power_v1_set_mode_event *ev = data; + bool enable = (ev->mode == ZWLR_OUTPUT_POWER_V1_MODE_ON); + wlr_output_enable(ev->output, enable); + wlr_output_commit(ev->output); + wwarning("wl_backend: output power set_mode %s", enable ? "ON" : "OFF"); +} + +/* ------------------------------------------------------------------ */ +/* Phase 34: text input v3, input method v2, virtual keyboard v1 */ +/* ------------------------------------------------------------------ */ + +/* + * handle_text_input_new — a client created a zwp_text_input_v3 object. + * Text input objects are managed entirely by wlroots; the compositor just + * needs to call wlr_text_input_v3_send_enter/leave to activate the IME + * for a focused surface. For now we log and do nothing — the IME framework + * will still register; full integration (enter on focus) can be added later. + */ +static void +handle_text_input_new(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wwarning("wl_backend: text input v3 object created"); +} + +/* + * handle_input_method_new — an IME server (ibus, fcitx5) connected. + * The input method protocol is symmetric with text_input; wlroots handles + * the protocol exchange. We log its arrival. + */ +static void +handle_input_method_new(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wwarning("wl_backend: input method v2 connected"); +} + +/* + * handle_virtual_keyboard_new — a client created a virtual keyboard device. + * wlroots has already created the wlr_virtual_keyboard_v1 and wired its + * input_device into the backend. We just log it; the seat will advertise + * the keyboard capability automatically via handle_backend_new_input. + */ +static void +handle_virtual_keyboard_new(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wwarning("wl_backend: virtual keyboard v1 created"); +} + +/* ------------------------------------------------------------------ */ +/* Phase 35: foreign toplevel management + xdg_activation */ +/* ------------------------------------------------------------------ */ + +/* + * handle_xdg_activation_request — a client requests focus for a surface + * using an activation token (xdg_activation_v1 protocol). + * Find the view owning that surface, deliver WM keyboard focus to it, and + * raise it to the top of the stacking order. + */ +static void +handle_xdg_activation_request(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_xdg_activation_v1_request_activate_event *ev = data; + if (!ev->surface) + return; + + /* Walk toplevel list to find a view whose wlr_surface matches. */ + struct wl_toplevel_view *view; + wl_list_for_each(view, &wl_state.toplevel_list, link) { + struct wlr_surface *surf = NULL; + if (view->xdg_surface) + surf = view->xdg_surface->surface; + else if (view->xw_surface) + surf = view->xw_surface->surface; + if (surf != ev->surface) + continue; + + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + wSetFocusTo(wScreenWithNumber(0), wwin); + if (view->scene_node) + wlr_scene_node_raise_to_top(view->scene_node); + /* Move to front of toplevel list (Z-order). */ + wl_list_remove(&view->link); + wl_list_insert(wl_state.toplevel_list.prev, &view->link); + /* Update foreign toplevel activated state. */ + if (view->ft_handle) + wlr_foreign_toplevel_handle_v1_set_activated(view->ft_handle, true); + wwarning("wl_backend: xdg_activation focus granted id=0x%lx", view->id); + } + return; + } + wwarning("wl_backend: xdg_activation request for unknown surface (ignored)"); +} + +/* ------------------------------------------------------------------ */ +/* Phase 36: layer shell — panels, docks, wallpapers, overlays */ +/* ------------------------------------------------------------------ */ + +#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); + 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); + 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 if available. */ + if (wl_state.scene) { + lv->scene_node = wlr_scene_subsurface_tree_create( + &wl_state.scene->node, 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, 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->events.map, &lv->map_listener); + wl_signal_add(&ls->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 : ""); +} + +/* ------------------------------------------------------------------ */ +/* Phase 37: data control, input inhibitor, virtual pointer, idle, */ +/* xdg-foreign */ +/* ------------------------------------------------------------------ */ + +/* + * handle_input_inhibit_activate — a lock screen client grabbed exclusive input. + * We log it; a real lock screen implementation would also hide all other + * surfaces and prevent any focus delivery outside the lock client. + */ +static void +handle_input_inhibit_activate(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wwarning("wl_backend: input inhibitor activated (lock screen grabbed input)"); +} + +/* + * handle_input_inhibit_deactivate — the lock screen released exclusive input. + */ +static void +handle_input_inhibit_deactivate(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wwarning("wl_backend: input inhibitor deactivated (lock screen released input)"); +} + +/* + * handle_virtual_pointer_new — a client created a virtual pointer device + * (e.g. a VNC / remote desktop server injecting cursor events). + */ +static void +handle_virtual_pointer_new(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + wwarning("wl_backend: virtual pointer created"); +} + static void handle_pointer_button(struct wl_listener *listener, void *data) { @@ -2328,7 +3203,25 @@ wl_update_pointer_focus(uint32_t time_msec) } wlr_seat_pointer_clear_focus(wl_state.seat); } - (void)view; /* future: WM hit-testing / raise-on-hover */ + /* Phase 29 (FFP): focus-follows-pointer — when the pointer enters a new + * managed view, deliver WM keyboard focus without raising. + * Only fires when wPreferences.focus_mode == WKF_SLOPPY. */ + { + static struct wl_toplevel_view *last_ffp_view = NULL; + if (view != last_ffp_view) { + last_ffp_view = view; + if (view) { + WObjDescriptor *desc = + wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + if (wPreferences.focus_mode == WKF_SLOPPY) { + wSetFocusTo(wScreenWithNumber(0), wwin); + } + } + } + } + } } static void @@ -2952,10 +3845,8 @@ static void wl_fake_leader_destroy(WNativeWindow win) { (void)win; } /* Screen internals */ /* ------------------------------------------------------------------ */ -/* Wayland compositor is always the sole WM — succeed unconditionally. */ -static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; } -/* No X11 event mask selection needed; succeed. */ -static int wl_screen_select_input(WScreen *scr, long event_mask) { (void)scr; (void)event_mask; return 1; } +static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; } +static int wl_screen_select_input(WScreen *scr, long mask) { (void)scr; (void)mask; return 1; } static void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; } static void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; } static void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; } @@ -2963,28 +3854,18 @@ static void wl_screen_alloc_gcs(WScreen *scr) { (void)scr; } static void wl_screen_create_internals(WScreen *scr) { - /* Phase 62: no_focus_win sentinel — WNATIVE_NONE (0) is the Wayland - * equivalent of the X11 InputOnly grab window. Clear keyboard focus - * from the seat so the WM starts in the "nobody focused" state. - * Enable auto_focus so wManageWindow calls wSetFocusTo for new windows, - * ensuring wwin->flags.focused and the full WM focus lifecycle work. */ scr->no_focus_win = WNATIVE_NONE; wPreferences.auto_focus = 1; if (wl_state.seat) wlr_seat_keyboard_clear_focus(wl_state.seat); - /* Phase 77: initialise modifier masks to standard XKB/X11 values so - * that wXModifierFromKey("ALT") etc. return non-zero values. - * Mod1Mask=8 (Alt), Mod4Mask=64 (Super/Win); Meta aliases Alt. */ wSetModifierMasks(Mod1Mask, Mod1Mask, Mod4Mask, 0); - /* Phase 11: disable clip/dock in Wayland mode — wDockCreate requires - * X11 rendering resources (GCs, pixmaps) that are unavailable without - * dpy. Setting noclip/nodock prevents wWorkspaceNew from calling - * wDockCreate when additional workspaces are created. */ + /* Dock/clip require X11 rendering resources not yet available in Wayland + * mode. Disable until native Wayland dock rendering is implemented. */ wPreferences.flags.noclip = 1; wPreferences.flags.nodock = 1; - wwarning("wl_backend: dock/clip disabled in Wayland mode (noclip=1 nodock=1)"); + wwarning("wl_backend: dock/clip disabled pending native Wayland rendering"); } static void wl_screen_create_pixmaps(WScreen *scr) { (void)scr; } static void wl_screen_load_tech_font(WScreen *scr) { (void)scr; } @@ -3118,13 +3999,18 @@ static void wl_extensions_init(void) {} static void wl_cursors_init(void) {} static WNativeWindow wl_cursor_create_named(const char *name) { (void)name; return WNATIVE_NONE; } static void wl_shape_combine_mask(WNativeWindow win, WNativeWindow mask) { (void)win; (void)mask; } -static void wl_register_error_handlers(void) {} +static void +wl_register_error_handlers(void) +{ + /* Pure Wayland: no X11 Display*, so no Xlib error handlers needed. */ +} /* ------------------------------------------------------------------ */ /* vtable */ /* ------------------------------------------------------------------ */ const WMBackend wl_backend = { + .is_wayland = 1, /* Path 1: distinguishes Wayland from X11 */ .display_open = wl_display_open, .display_close = wl_display_close, .display_screen_count = wl_display_screen_count, diff --git a/src/backend/x11/x11_backend.c b/src/backend/x11/x11_backend.c index 79f7191..2786caf 100644 --- a/src/backend/x11/x11_backend.c +++ b/src/backend/x11/x11_backend.c @@ -120,6 +120,12 @@ x11_event_loop_run(void) /* no-op: EventLoop() in event.c drives the X11/WINGs event pump */ } +static void +x11_event_loop_terminate(void) +{ + /* no-op: X11 EventLoop exits via exit() directly */ +} + /* -------------------------------------------------------------------- */ /* Screen lifecycle */ /* -------------------------------------------------------------------- */ @@ -1403,7 +1409,7 @@ x11_client_query_shaped(WNativeWindow win, int *shaped_out) #define REPLACE_WM_TIMEOUT 15 #endif -static int +int x11_screen_replace_wm(WScreen *scr) { #ifdef USE_ICCCM_WMREPLACE @@ -1507,7 +1513,7 @@ alreadyRunningErrorX11(Display *d, XErrorEvent *error) return -1; } -static int +int x11_screen_select_input(WScreen *scr, long event_mask) { XErrorHandler oldHandler; @@ -1561,7 +1567,7 @@ x11_screen_setup_noticeboard(WScreen *scr) (unsigned char *)&iw, 1); } -static void +void x11_screen_alloc_gcs(WScreen *scr) { XGCValues gcv; @@ -2338,9 +2344,15 @@ x11_catch_error(Display *edpy, XErrorEvent *error) char buffer[MAXLINE]; /* ignore some errors */ + + /* BadMatch on XSetInputFocus: window may not be viewable yet (e.g. during + * XWayland startup before surfaces are mapped). Silence unconditionally — + * the window ID may be None when focus is set to PointerRoot or None. */ + if (error->error_code == BadMatch && error->request_code == X_SetInputFocus) + return 0; + if (error->resourceid != None && ((error->error_code == BadDrawable && error->request_code == X_GetGeometry) - || (error->error_code == BadMatch && (error->request_code == X_SetInputFocus)) || (error->error_code == BadWindow) /* && (error->request_code == X_GetWindowAttributes @@ -2383,12 +2395,14 @@ x11_register_error_handlers(void) /* -------------------------------------------------------------------- */ const WMBackend x11_backend = { + .is_wayland = 0, /* X11 backend: not Wayland */ .display_open = x11_display_open, .display_close = x11_display_close, .display_screen_count = x11_display_screen_count, .display_default_screen = x11_display_default_screen, .atom_intern = x11_atom_intern, .event_loop_run = x11_event_loop_run, + .event_loop_terminate = x11_event_loop_terminate, .screen_open = x11_screen_open, .screen_close = x11_screen_close, .frame_create_toplevel = x11_frame_create_toplevel, diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 6e69b50..601f69f 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -118,11 +118,14 @@ # so handle_backend_new_output finds a valid scene when it fires. # Verified by "scene graph created" + "scene output created" + # "headless compositor mode" logs. -# 39. (Phase 17) XWayland display allocated (lazy start): wlr_xwayland_create() -# is called with lazy=true; Xwayland only starts on first X11 client. -# Compositor logs "XWayland DISPLAY=: (lazy: starts on first X11 client)". -# DISPLAY env var is set from the pre-allocated display number. -# Verified by "XWayland DISPLAY=.*lazy" log. +# 39. (Path 1) XWayland eager start + Display* bridge: wlr_xwayland_create() +# called with lazy=false; ready signal fires when Xwayland socket accepts +# connections; handle_xwayland_ready() opens Display* via XOpenDisplay(). +# wl_display_open() pumps event loop until Display* set (up to 10 s), +# then returns it to main.c so WindowMaker's X11 UI renders through Xwayland. +# Compositor logs "XWayland starting on" + "XWayland Display* ready" or +# "XWayland ready timeout" (headless/no-Xwayland envs). +# Verified by "XWayland starting on" log. # 43. (Phase 24) Click-to-focus: on WLR_BUTTON_PRESSED, view_surface_at() # finds the view under the pointer; wSetFocusTo delivers WM keyboard # focus and wlr_scene_node_raise_to_top raises it in Z-order. @@ -147,6 +150,15 @@ # handlers now start a compositor-side grab that tracks pointer motion # and repositions/resizes the window in the scene graph. Button release # ends the grab. Build-only check (no client sends request_move here). +# 48. (Phase 29) Scroll wheel: pointer->events.axis wired for both headless +# and real nested pointer devices → wlr_seat_pointer_notify_axis; axis +# events dropped during compositor grab. Focus-follows-pointer: when +# wPreferences.focus_mode==WKF_SLOPPY, wSetFocusTo called on pointer enter. +# 49. (Phase 29) wp_viewporter created (mpv, Chromium, Firefox video scaling). +# 50. (Phase 30) zwp_relative_pointer_manager_v1 + zwp_pointer_constraints_v1 +# created; new_constraint activates immediately so game pointer-lock works. +# 51. (Phase 31) zwp_keyboard_shortcuts_inhibit_manager_v1 created; new +# inhibitor activated immediately so fullscreen apps capture all keys. # 45. (Phase 26) wl_subcompositor already advertised by wlr_compositor_create # (embedded in wlr_compositor struct); verified via weston-info. # zwp_primary_selection_device_manager_v1 created; seat @@ -219,7 +231,7 @@ WMAKER_USE_WAYLAND=1 src/wmaker \ --for-real 2>/tmp/wl-smoke-stderr & WM_PID=$! -# Wait up to 5s for wmaker to reach the event loop (stay alive) +# Wait up to 5s for wmaker to stay alive (basic liveness check) i=0 while [ $i -lt 50 ]; do kill -0 "$WM_PID" 2>/dev/null || break @@ -230,6 +242,21 @@ done kill -0 "$WM_PID" 2>/dev/null \ || die "wmaker exited prematurely before SIGTERM (crashed during startup)" +# Path 1: with XWayland eager start, wl_display_open() pumps the event loop +# until XWayland signals ready (up to 5 s), then wScreenInit / startup.c +# run before the main event loop begins. Wait up to 15 s for the "event +# loop running" sentinel so that all post-startup log messages are present +# before we grep for them. +i=0 +while [ $i -lt 150 ]; do + grep -q 'event loop running' /tmp/wl-smoke-stderr 2>/dev/null && break + kill -0 "$WM_PID" 2>/dev/null || break + sleep 0.1 + i=$(( i + 1 )) +done +grep -q 'event loop running' /tmp/wl-smoke-stderr 2>/dev/null \ + || die "wmaker did not reach event loop within 15 s (XWayland pump timeout?)" + # -- 4. Wayland compositor socket must exist in XDG_RUNTIME_DIR ----------- # # Phase 34: wl_display_open() calls wl_display_add_socket_auto() and logs: @@ -295,25 +322,70 @@ echo " ptr listener check: pointer motion/button/frame listeners wired" # -- 4f. Phase 11: dock/clip disabled in Wayland mode -------------------- # -# wl_screen_create_internals sets noclip=1 and nodock=1 so wWorkspaceNew -# does not call wDockCreate (which requires X11 rendering resources). -grep -q 'dock/clip disabled in Wayland mode' /tmp/wl-smoke-stderr \ - || die "compositor did not log dock/clip disabled (noclip/nodock not set in wl_screen_create_internals)" -echo " nodock check: dock/clip disabled in Wayland mode (noclip=1 nodock=1)" +# wl_screen_create_internals always sets noclip=1 nodock=1 until native +# Wayland dock rendering is implemented. +grep -q 'dock/clip disabled' /tmp/wl-smoke-stderr \ + || die "compositor did not log dock/clip disabled (wl_screen_create_internals broken?)" +echo " nodock check: dock/clip disabled pending native Wayland rendering" # -- 4f2b. Phase 25: clipboard selection wired ---------------------------- grep -q 'clipboard selection wired' /tmp/wl-smoke-stderr \ || die "compositor did not log clipboard wired (seat request_set_selection listener missing)" echo " clipboard check: clipboard selection wired" -# -- 4f2c. Phases 26-28: primary selection + xdg-output + idle-inhibit ---- +# -- 4f2c. Phases 26-31: additional protocol managers ------------------- grep -q 'primary selection.*wired' /tmp/wl-smoke-stderr \ || die "compositor did not log primary selection wired" grep -q 'xdg-output-manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log xdg-output-manager created" grep -q 'idle-inhibit manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log idle-inhibit manager created" -echo " protocols check: primary-selection, xdg-output, idle-inhibit created" +grep -q 'wp_viewporter created' /tmp/wl-smoke-stderr \ + || die "compositor did not log wp_viewporter created" +grep -q 'relative-pointer-manager created' /tmp/wl-smoke-stderr \ + || die "compositor did not log relative-pointer-manager created" +grep -q 'pointer-constraints created' /tmp/wl-smoke-stderr \ + || die "compositor did not log pointer-constraints created" +grep -q 'keyboard-shortcuts-inhibit created' /tmp/wl-smoke-stderr \ + || die "compositor did not log keyboard-shortcuts-inhibit created" +grep -qE 'linux-dmabuf created|linux-dmabuf unavailable' /tmp/wl-smoke-stderr \ + || die "compositor did not log linux-dmabuf status (zwp_linux_dmabuf_v1)" +WL_DMABUF_STATUS=$(grep -oE 'linux-dmabuf created|linux-dmabuf unavailable' /tmp/wl-smoke-stderr | head -1) +grep -q 'screencopy manager created' /tmp/wl-smoke-stderr \ + || die "compositor did not log screencopy manager created (zwlr_screencopy_manager_v1)" +grep -q 'export-dmabuf manager created' /tmp/wl-smoke-stderr \ + || die "compositor did not log export-dmabuf manager created (zwlr_export_dmabuf_manager_v1)" +grep -q 'output-management created' /tmp/wl-smoke-stderr \ + || die "compositor did not log output-management created (zwlr_output_management_v1)" +grep -q 'output-power-management created' /tmp/wl-smoke-stderr \ + || die "compositor did not log output-power-management created (zwlr_output_power_management_v1)" +grep -q 'gamma-control created' /tmp/wl-smoke-stderr \ + || die "compositor did not log gamma-control created (zwlr_gamma_control_manager_v1)" +grep -q 'presentation-time created' /tmp/wl-smoke-stderr \ + || die "compositor did not log presentation-time created (wp_presentation)" +grep -q 'text-input-v3 created' /tmp/wl-smoke-stderr \ + || die "compositor did not log text-input-v3 created (zwp_text_input_manager_v3)" +grep -q 'input-method-v2 created' /tmp/wl-smoke-stderr \ + || die "compositor did not log input-method-v2 created (zwp_input_method_manager_v2)" +grep -q 'virtual-keyboard-v1 created' /tmp/wl-smoke-stderr \ + || die "compositor did not log virtual-keyboard-v1 created (zwp_virtual_keyboard_manager_v1)" +grep -q 'foreign-toplevel-management created' /tmp/wl-smoke-stderr \ + || die "compositor did not log foreign-toplevel-management created" +grep -q 'xdg-activation created' /tmp/wl-smoke-stderr \ + || die "compositor did not log xdg-activation created (xdg_activation_v1)" +grep -q 'layer-shell created' /tmp/wl-smoke-stderr \ + || die "compositor did not log layer-shell created (zwlr_layer_shell_v1)" +grep -q 'data-control created' /tmp/wl-smoke-stderr \ + || die "compositor did not log data-control created (zwlr_data_control_manager_v1)" +grep -q 'input-inhibitor created' /tmp/wl-smoke-stderr \ + || die "compositor did not log input-inhibitor created (zwlr_input_inhibitor_v1)" +grep -q 'virtual-pointer created' /tmp/wl-smoke-stderr \ + || die "compositor did not log virtual-pointer created (zwlr_virtual_pointer_manager_v1)" +grep -q 'idle created' /tmp/wl-smoke-stderr \ + || die "compositor did not log idle created (org_kde_kwin_idle)" +grep -q 'xdg-foreign-v2 created' /tmp/wl-smoke-stderr \ + || die "compositor did not log xdg-foreign-v2 created" +echo " protocols check: primary-sel, xdg-output, idle-inhibit, viewporter, relative-ptr, ptr-constraints, kbd-inhibit, $WL_DMABUF_STATUS, screencopy, export-dmabuf, output-mgmt, output-power, gamma, presentation, text-input, input-method, virt-kbd, foreign-toplevel, xdg-activation, layer-shell, data-control, input-inhibitor, virt-ptr, idle, xdg-foreign" # -- 4f3. Phase 23: cursor created and xcursor theme loaded --------------- grep -q 'cursor created, xcursor theme loaded' /tmp/wl-smoke-stderr \ @@ -322,16 +394,15 @@ grep -q "cursor image set to 'left_ptr'" /tmp/wl-smoke-stderr \ || die "compositor did not log cursor image set (wlr_xcursor_manager_set_cursor_image not called)" echo " cursor check: wlr_cursor created, xcursor 'left_ptr' set on output" -# -- 4g. Phase 17: XWayland display number allocated (lazy start) -------- +# -- 4g. Path 1: XWayland eager start + Display* acquisition -------------- # -# wl_display_open() logs "XWayland DISPLAY=: (lazy: starts on first -# X11 client)" when wlr_xwayland_create() succeeds with lazy=true. -# The Xwayland process does not start until the first X11 client connects, -# avoiding the startup race that caused "Broken pipe" with lazy=false. -# (Falls back gracefully if XWayland is unavailable.) -if grep -q 'XWayland DISPLAY=.*lazy' /tmp/wl-smoke-stderr; then +# wl_display_open() now uses lazy=false (eager start) so Xwayland starts +# at compositor boot. The ready signal fires once the socket accepts +# XWayland is started for X11 client support. The WM's own UI no longer +# uses the XWayland Display* — native Wayland rendering is used instead. +if grep -q 'XWayland starting DISPLAY=' /tmp/wl-smoke-stderr; then XDISP=$(grep -o 'DISPLAY=:[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/DISPLAY=//') - echo " xwayland check: XWayland DISPLAY=$XDISP (lazy start)" + echo " xwayland check: XWayland started DISPLAY=$XDISP (X11 clients can connect)" elif grep -q 'XWayland unavailable' /tmp/wl-smoke-stderr; then echo " xwayland check: XWayland unavailable (skipped)" else @@ -377,6 +448,17 @@ if command -v weston-info >/dev/null 2>&1; then # Phase 64: zxdg_decoration_manager_v1 must be in the global registry echo "$WI" | grep -q 'zxdg_decoration_manager_v1' \ || die "zxdg_decoration_manager_v1 not in Wayland global registry" + # Phase 29: wp_viewporter (mpv, Chromium, Firefox video scaling) + echo "$WI" | grep -q 'wp_viewporter' \ + || die "wp_viewporter not in Wayland global registry" + # Phase 30: relative pointer + pointer constraints (games) + echo "$WI" | grep -q 'zwp_relative_pointer_manager_v1' \ + || die "zwp_relative_pointer_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'zwp_pointer_constraints_v1' \ + || die "zwp_pointer_constraints_v1 not in Wayland global registry" + # Phase 31: keyboard shortcuts inhibitor (fullscreen apps) + echo "$WI" | grep -q 'zwp_keyboard_shortcuts_inhibit_manager_v1' \ + || die "zwp_keyboard_shortcuts_inhibit_manager_v1 not in Wayland global registry" # Phase 26: wl_subcompositor (subsurfaces for Chromium, Firefox, etc.) echo "$WI" | grep -q 'wl_subcompositor' \ || die "wl_subcompositor not in Wayland global registry" @@ -389,8 +471,60 @@ if command -v weston-info >/dev/null 2>&1; then # Phase 28: idle-inhibit (video players, games) echo "$WI" | grep -q 'zwp_idle_inhibit_manager_v1' \ || die "zwp_idle_inhibit_manager_v1 not in Wayland global registry" + # Phase 32: screencopy + export-dmabuf (always available) + echo "$WI" | grep -q 'zwlr_screencopy_manager_v1' \ + || die "zwlr_screencopy_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'zwlr_export_dmabuf_manager_v1' \ + || die "zwlr_export_dmabuf_manager_v1 not in Wayland global registry" + # Phase 32: linux-dmabuf only available with a real GPU renderer + if echo "$WI" | grep -q 'zwp_linux_dmabuf_v1'; then + WL_DMABUF_GLOBAL="zwp_linux_dmabuf present" + else + WL_DMABUF_GLOBAL="zwp_linux_dmabuf absent (headless renderer)" + fi + # Phase 33: output management, output power, gamma control, presentation time + echo "$WI" | grep -q 'zwlr_output_manager_v1' \ + || die "zwlr_output_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'zwlr_output_power_manager_v1' \ + || die "zwlr_output_power_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'zwlr_gamma_control_manager_v1' \ + || die "zwlr_gamma_control_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'wp_presentation' \ + || die "wp_presentation not in Wayland global registry" + # Phase 34: text input, input method, virtual keyboard + echo "$WI" | grep -q 'zwp_text_input_manager_v3' \ + || die "zwp_text_input_manager_v3 not in Wayland global registry" + echo "$WI" | grep -q 'zwp_input_method_manager_v2' \ + || die "zwp_input_method_manager_v2 not in Wayland global registry" + echo "$WI" | grep -q 'zwp_virtual_keyboard_manager_v1' \ + || die "zwp_virtual_keyboard_manager_v1 not in Wayland global registry" + # Phase 35: foreign toplevel management + xdg_activation + echo "$WI" | grep -q 'zwlr_foreign_toplevel_manager_v1' \ + || die "zwlr_foreign_toplevel_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'xdg_activation_v1' \ + || die "xdg_activation_v1 not in Wayland global registry" + # Phase 36: layer shell + echo "$WI" | grep -q 'zwlr_layer_shell_v1' \ + || die "zwlr_layer_shell_v1 not in Wayland global registry" + # Phase 37: data control, input inhibitor, virtual pointer, idle, xdg-foreign + echo "$WI" | grep -q 'zwlr_data_control_manager_v1' \ + || die "zwlr_data_control_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'zwlr_input_inhibit_manager_v1' \ + || die "zwlr_input_inhibit_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'zwlr_virtual_pointer_manager_v1' \ + || die "zwlr_virtual_pointer_manager_v1 not in Wayland global registry" + echo "$WI" | grep -q 'org_kde_kwin_idle' \ + || die "org_kde_kwin_idle not in Wayland global registry" + echo "$WI" | grep -qE 'zxdg_exporter_v2|zxdg_importer_v2' \ + || die "zxdg_exporter/importer_v2 not in Wayland global registry" echo " globals check: wl_compositor, wl_subcompositor, wl_seat, xdg_wm_base, wl_shm, wl_output," \ - "zxdg_decoration_manager_v1, zwp_primary_selection, zxdg_output, zwp_idle_inhibit present" + "zxdg_decoration, zwp_primary_selection, zxdg_output, zwp_idle_inhibit," \ + "wp_viewporter, zwp_relative_pointer, zwp_pointer_constraints, zwp_kbd_inhibit," \ + "zwlr_screencopy, zwlr_export_dmabuf, zwlr_output_mgmt, zwlr_output_power," \ + "zwlr_gamma_control, wp_presentation, zwp_text_input, zwp_input_method," \ + "zwp_virtual_keyboard, zwlr_foreign_toplevel, xdg_activation," \ + "zwlr_layer_shell, zwlr_data_control, zwlr_input_inhibit, zwlr_virtual_ptr," \ + "org_kde_kwin_idle, zxdg_exporter/importer_v2 present; $WL_DMABUF_GLOBAL" else echo " globals check: skipped (weston-info not installed)" fi