The global 'dpy' variable was set to the XWayland Display*, causing
120+ direct Xlib calls in the WM core (XMapWindow, XClearWindow, etc.)
to pass synthetic frame buffer IDs to XWayland — hanging the startup.
Fix: wl_display_open returns NULL so global dpy=NULL. All existing
'if (dpy)' guards in the WM core now correctly skip Xlib calls.
The Wayland backend uses wl_state.x_display directly for the few
XWayland operations it needs (atom interning, screen info, GC alloc
for the no-focus window).
This unblocks startup — the event loop now runs successfully.
Architecture: frames are pixman images committed to wlr_scene_buffer
nodes — no XWayland dependency for WM UI rendering.
Key changes:
- Add wl_frame_buf struct (pixman_image_t + wlr_buffer + scene_buffer)
- Implement wlr_buffer_impl for frame buffers (begin_data_ptr_access)
- frame_create_toplevel: allocates pixman buffer + scene node
- frame_create_child: returns synthetic ID (sub-regions of parent)
- frame_configure: repositions scene node, reallocates on resize
- frame_destroy: destroys scene node and drops buffer
- frame_paint: renders titlebar texture (via wTextureRenderImage),
title text (via WMDrawString on pixman WMDrawable), and resizebar
into the pixman buffer, then commits to scene graph
- Add frame_paint vtable slot to WMBackend (backend.h)
- wFrameWindowPaint: calls wm_backend->frame_paint first; falls
through to Xlib code only if backend returns 0
- window_map/unmap/move: handle frame buffers via scene node ops
- stacking_raise/lower: handle frame buffers
- window_get_rect: return frame buffer geometry
- Remove all XWayland calls from frame/window lifecycle
The OR (override_redirect) surface handling remains for XWayland
client windows that use override_redirect (tooltips, menus from
X11 apps). The WM's own UI is now pure Wayland.
Key changes in wl_backend.c:
- wl_atom_intern: call XInternAtom on XWayland display (was returning 0)
- Add wl_or_view struct to track override_redirect XWayland surfaces
- handle_xwayland_new_surface: composite OR surfaces via scene graph
- frame_create_toplevel/child: create real X11 windows on XWayland
- frame_destroy/configure: delegate to Xlib
- window_map/unmap/move/resize: fall through to Xlib for frame windows
- stacking_raise/lower: update scene graph + Xlib fallback
- event_flush/sync: call XFlush/XSync on XWayland display
- window_set_border/border_width/clear: delegate to Xlib
- window_get_rect: query XWayland geometry for non-toplevel windows
Note: frame creation via XWayland is a temporary bridge. The correct
architectural approach is native scene-buffer rendering with pixman.
wl_display_open now pumps the Wayland event loop until XWayland fires its
"ready" signal, then calls XOpenDisplay() and returns the Display* to the
core WM. This makes dpy non-NULL so screen.c takes the full X11 code path:
RCreateContext, WINGs screen, workspaces, root menus, and EWMH hints all
initialise via the WM's own XWayland connection.
- wl_handle_x11_events: new fd watcher registered on ConnectionNumber(dpy)
so WINGs menus, dialogs, and keyboard events are dispatched during
wl_display_run() via XPending/XNextEvent/WMHandleEvent
- wl_screen_open: sets root_win/depth/colormap from DefaultRootWindow(dpy)
when bridge is active so RCreateContext gets valid X11 screen values
- wl_screen_create_internals: creates a 1x1 info_window on XWayland for
_NET_SUPPORTING_WM_CHECK and clipboard selection ownership
- handle_xwayland_map: guard against w_global.screen_count==0 (fires before
StartUp() in edge cases) to avoid assert in wScreenWithNumber
- wl_display_close: removes fd watcher and calls XCloseDisplay before
killing XWayland to avoid stale fd events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented nested Wayland mode (inside sway) from starting:
1. The wlr_wl_backend fires new_input during wlr_backend_start(), before
wl_state.seat exists (seat is created later in wl_display_open).
wlr_seat_set_keyboard(NULL, kb) → SIGSEGV. Guard with if (seat).
2. Sway's initial xdg-shell configure sends width=height=0 ("compositor
chooses"). Without an explicit mode, wlr_output_commit_state tries to
create a 0×0 GBM swapchain → crash in gbm_bo_create. Fall back to
1920×1080 when no preferred mode is available.
Also install XSetErrorHandler at the top of wl_display_open so the monitor
process's crash-dialog BadMatch (from sway's internal XWayland at :0) is
suppressed rather than printed in Xlib default format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Wayland backend runs with a live XWayland Display* once XWayland
fires its ready signal. Without an error handler, benign X errors
(BadMatch on X_SetInputFocus, BadWindow during window teardown) reach
Xlib's default handler and produce noisy output.
Add wl_catch_xwayland_error() — same permissive policy as the X11
backend's x11_catch_error() — and install it via XSetErrorHandler in
wl_register_error_handlers(). Include <X11/Xproto.h> for the
X_SetInputFocus request code constant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented XWayland client windows from appearing:
1. flags.mapped never set for XWayland windows (window.c)
The condition `else if (!dpy)` is false when dpy is the XWayland
Display* (it is only true for pure native-Wayland mode with no X11).
Changed to `else if (!dpy || wm_backend->is_wayland)` so XWayland
windows also get flags.mapped=1 after wManageWindow, matching the
semantics of XDG toplevel windows.
2. scene node positioned at xw->x,xw->y before configure round-trip
handle_xwayland_map used xw->x,xw->y for the initial scene node
position. These values may not yet reflect the configure sent in
handle_new_xwayland_surface (the X11 round-trip is async). Changed
to use view->x,view->y which are set to output/4 in
handle_new_xwayland_surface and kept in sync by set_geometry listener.
Also added a log line when xw->surface is NULL at map time so the cause
is diagnosable from wmaker stderr.
Note: if xterm connects to the wrong DISPLAY (e.g. Sway's :1 instead of
wmaker's :2), no handle_xwayland_map fires at all. Check wmaker's
"XWayland ready on :X" log line for the correct display number.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Workspace switching (wl_window_map / wl_window_unmap):
wl_window_map and wl_window_unmap were stubs. Both now look up the
wl_toplevel_view by its WNativeWindow ID and call
wlr_scene_node_set_enabled(scene_node, true/false). The policy layer
(workspace.c wWorkspaceForceChange) already calls wWindowMap/wWindowUnmap
for each window on workspace switch, so no policy changes are needed —
the scene node enable/disable is the only missing piece.
Dock icon rendering (Phase 30+):
Added a 32-entry dock_apps[] array to wl_compositor_state tracking
each managed window's ID and ARGB8888 icon pixels.
wm_dock_load_icon() reads _NET_WM_ICON from XWayland windows via
get_window_image_from_x11() (wmspec.c), converts wrlib RImage RGBA
bytes to ARGB8888, and returns a heap buffer. XDG native Wayland
clients (no X11 connection) get NULL → empty tile placeholder.
wm_dock_refresh() repaints the dock background + tiles, then for each
managed app composites its icon into the tile using pixman BILINEAR
scaling (icon_w × icon_h → tile_sz-2 inset area), then calls
wm_shm_surface_repaint() which destroys and recreates the scene_buffer
node to force wlroots 0.15 to upload the fresh texture on the next
frame (wlroots caches textures by wlr_buffer pointer; destroy+recreate
is the safe invalidation path for this API version).
wm_dock_app_add() is called from handle_toplevel_map and
handle_xwayland_map after wManageWindow succeeds.
wm_dock_app_remove() is called from handle_toplevel_unmap and
handle_xwayland_unmap before wUnmanageWindow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace async-signal-unsafe SIGTERM handling with signalfd registered in
the wlroots event loop. The traditional path (startup.c handleExitSig →
DispatchEvent → wl_event_loop_terminate → pipe write) works in headless
mode but races in nested-Wayland mode: the combination of SA_RESTART and
epoll ordering could cause the pipe write to go unnoticed until the next
event, leaving SIGTERM apparently ignored in interactive Sway sessions.
wl_event_loop_run() now:
1. Creates a signalfd(2) for SIGTERM, SIGINT, SIGHUP with SFD_NONBLOCK
2. Blocks those signals from async delivery (sigprocmask SIG_BLOCK)
3. Registers the fd in the wlroots event loop via wl_event_loop_add_fd
4. wl_signalfd_handler() reads the pending signal and calls
wl_display_terminate() from within loop context — fully synchronous,
no async-signal-safety concerns
The pipe-based wl_event_loop_terminate() is retained as a secondary
mechanism for code paths that call it outside the main event loop.
Cleanup (event source removal + fd close) happens in wl_display_close().
Smoke test updated with a new "signal check" that verifies the log line
"signalfd installed for SIGTERM/SIGINT/SIGHUP" before sending SIGTERM.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the Phase 29 test square with a proper dock panel anchored to the
right edge of the primary output. The panel is rendered entirely via
pixman into a wm_shm_surface and uploaded to the wlroots scene graph — no
X11 drawing resources needed.
Visual design:
- Dark charcoal background (0xFF1C1C1C)
- 1-px left-edge highlight (0xFF404040) separating dock from desktop
- 1-px top separator (0xFF303030)
- Column of bevelled icon-slot placeholders (top/left highlight +
bottom/right shadow) at icon_size × icon_size spacing
Layout and sizing:
- Width = icon_size + 4 px (default 68 px for 64-px icons)
- Height = full output height
- X position = output_width - dock_width (flush to right edge)
- n_slots = (height - 4) / (icon_size + 2) (16 slots @ 1080p)
Usable area:
- head-0 x2 shrunk by dock_width so window placement never overlaps
The WDock X11 code path remains disabled (nodock=1) — icon population
and click handling come in later phases.
Smoke test: PASS ("dock panel created at 1852,0 (68x1080, 16 slots)")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WL_SHM_FORMAT_ARGB8888 == 0, which the DRM/GLES2 renderer interprets as
DRM_FORMAT_INVALID. This caused:
- "Unsupported pixel format 0x0" on the first frame upload attempt
- wlr_texture_from_buffer assertion failure when the scene renderer
retried the upload lazily during an active render pass
Use DRM_FORMAT_ARGB8888 (0x34325241) from <libdrm/drm_fourcc.h> instead.
Texture creation now succeeds at wlr_scene_buffer_set_buffer time, so
the lazy path is never reached.
Verified: steel-blue test square visible in nested Sway session; smoke
test PASS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add wm_shm_surface: a compositor-owned pixel buffer that bridges pixman
software rendering to the wlroots scene graph.
The struct embeds a wlr_buffer (wl_container_of compatible) so wlroots
can reference-count and GPU-upload the pixel data on demand via the
wlr_buffer_impl vtable (begin_data_ptr_access / end_data_ptr_access /
destroy). A pixman_image_t wrapping the same malloc memory provides the
drawing API.
Four entry points:
wm_shm_surface_create — alloc + wlr_buffer_init + pixman_image_create
wm_shm_surface_fill — pixman_image_composite32(PIXMAN_OP_SRC, ...)
wm_shm_surface_commit — wlr_scene_buffer_create / set_position
wm_shm_surface_destroy — scene node + wlr_buffer_drop (async free)
A 64×64 steel-blue (0xFF4080C0) test surface is committed to the root
scene node at (16,16) inside wl_screen_create_internals, proving the
pixman → wlr_scene_buffer upload pipeline works end-to-end.
Build fixes:
- Add pixman-1 to PKG_CHECK_MODULES in configure.ac so -lpixman-1
appears in WLROOTS_LIBS (linker was missing the symbol).
- Add -I$(srcdir)/backend/wayland to AM_CPPFLAGS in Makefile.am so
generated Wayland protocol headers are found during compilation.
Smoke test: PASS (all existing checks continue to pass; Phase 29 log
line "Phase 29 test surface committed" confirms the pipeline fires).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the attempt to use XWayland as the WM's own rendering display.
All Path 1 code is stripped:
- wl_display_open now returns NULL (pure Wayland, no X11 Display*)
- handle_xwayland_ready only sets DISPLAY for X11 client apps; no XOpenDisplay
- xwayland_dpy / xwayland_dpy_source / xwayland_failed fields removed
- xwayland_dpy_drain_cb (X11 event drain) removed
- wl_screen_replace_wm / select_input / alloc_gcs / register_error_handlers
simplified to pure-Wayland no-ops; x11_backend.h Path 1 exports removed
- x11_register_error_handlers reverted to static
- x11_catch_error BadMatch fix kept (good cleanup regardless)
- wl_screen_create_internals always disables dock/clip pending native Wayland
- Smoke test updated to expect dock/clip-disabled unconditionally
XWayland is still started by wlroots for X11 client application support.
The WM's own UI (dock, title bars, menus) will be rendered natively via
wl_shm + pixman surfaces in subsequent commits.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Phase 26 — wl_subcompositor + primary selection (middle-click paste):
wl_subcompositor is already advertised by wlr_compositor_create (the
wlr_subcompositor is embedded in wlr_compositor); confirmed via weston-info.
zwp_primary_selection_device_manager_v1 created via
wlr_primary_selection_v1_device_manager_create(). seat.request_set_primary_
selection wired to handle_seat_request_primary_selection which calls
wlr_seat_set_primary_selection, so text selected in one client is available
for middle-click paste in any other client.
Phase 27 — zxdg_output_manager_v1:
wlr_xdg_output_manager_v1_create(display, output_layout) advertises the
xdg-output protocol. Panel clients (waybar, etc.) and HiDPI-aware apps
use this to query the logical position and size of each output, which may
differ from the physical pixel dimensions under scaling.
Phase 28 — zwp_idle_inhibit_manager_v1:
wlr_idle_inhibit_v1_create() advertises the idle-inhibit protocol. The
new_inhibitor signal is wired to handle_idle_new_inhibitor which logs the
event; the inhibitor list on wl_state.idle_inhibit_mgr can be queried by
a future idle/DPMS implementation to suppress screen blanking while any
inhibitor is live.
Smoke test: "protocols check" verifies all three startup log lines; the
weston-info globals check now also asserts wl_subcompositor,
zwp_primary_selection_device_manager_v1, zxdg_output_manager_v1, and
zwp_idle_inhibit_manager_v1 are present (25/25 checks pass).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 24a — click-to-focus + raise-on-click:
handle_pointer_button now calls view_surface_at() on WLR_BUTTON_PRESSED.
If a managed view is hit, wSetFocusTo() delivers WM keyboard focus and
wlr_scene_node_raise_to_top() raises the surface in Z-order. The view
is also moved to the tail of toplevel_list so view_surface_at() returns
it first on subsequent pointer hits. stacking.h included for the raise
path (wRaiseFrame not yet needed but header is now available).
Phase 24b — client cursor surface (wl_pointer.set_cursor):
seat->events.request_set_cursor wired to handle_seat_request_cursor.
The handler validates the request comes from the currently pointer-focused
client (rejects unfocused clients), then calls wlr_cursor_set_surface so
the application-supplied cursor image is rendered on all outputs. In
nested mode this updates the host compositor's cursor via set_cursor.
Phase 25 — clipboard / wl_data_device selection forwarding:
seat->events.request_set_selection wired to handle_seat_request_set_selection.
The handler calls wlr_seat_set_selection, making the client's data source
available to subsequent paste operations in any other client that reads
the seat's selection. Logged as "clipboard selection wired" at startup.
Smoke test: new "clipboard check" verifies startup log; 24/24 pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire up wlr_cursor and wlr_xcursor_manager so the cursor is visible when
running as a nested Wayland compositor.
- wlr_cursor created in wl_display_open and attached to the output layout.
- wlr_xcursor_manager created with NULL (system default) theme at 24 px
base size; wlr_xcursor_manager_load called at scale 1.0 at open time.
- In handle_backend_new_output, after the output joins the layout:
- Load xcursor at the output's actual scale factor.
- Set "left_ptr" as the initial cursor image.
- Warp the hardware cursor to the current virtual pointer position.
In nested mode this causes wlroots to send wl_pointer.set_cursor to
the host compositor, making the cursor visible in the window.
- All pointer motion handlers (relative, absolute) and wl_pointer_warp /
wl_pointer_warp_to now call wlr_cursor_warp after updating pointer_x/y,
keeping the hardware cursor in sync with the virtual pointer position.
handle_pointer_motion_absolute uses wlr_cursor_warp_absolute and reads
back cursor->x/y for the pixel position (correct in all output layouts).
- Grab cursor shapes: wl_grab_begin_move sets "fleur"; wl_grab_begin_resize
selects an edge-appropriate name (nw-resize, se-resize, etc.) via a new
resize_edges_to_cursor() helper; wl_grab_end restores "left_ptr".
- wl_display_close destroys xcursor_mgr then cursor before display teardown.
Smoke test: new "cursor check" verifies "cursor created" + "cursor image
set to 'left_ptr'" log lines (23/23 checks pass).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a compositor-side pointer grab so clients can interactively move and
resize their windows. When an XDG toplevel sends request_move or
request_resize, the compositor records the current pointer position and
window geometry, then routes all subsequent motion events directly to
wl_grab_apply_motion() instead of the normal pointer-focus path.
- wl_grab_begin_move / wl_grab_begin_resize: snapshot grab origin,
clear seat pointer focus so the client stops receiving motion events.
- wl_grab_apply_motion: on every motion event, compute dx/dy from the
grab origin and reposition (MOVE) or stretch (RESIZE) the scene node.
Resize honours all WL_SHELL_SURFACE_RESIZE_* edge combinations.
Minimum window size clamped to 32×32 px. XWayland windows use
wlr_xwayland_surface_configure; XDG toplevels use set_size + schedule_configure.
- wl_grab_end: clears grab state; any button release while a grab is
active ends it and resumes normal pointer-focus delivery.
- grab_type, grab_view, grab_start_{ptr,win}_{x,y,w,h}, grab_resize_edges
added to wl_compositor_state.
Smoke test: build-only check added (no existing test client emits
request_move); all 22/22 checks continue to pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch wlr_xwayland_create from lazy=false to lazy=true. With eager
start the Xwayland process forks before the compositor's event loop is
running; Xwayland's Wayland client tries to negotiate globals immediately,
gets no response, and closes its connection ("Broken pipe" / "xcb connect
failed: 1"). Lazy mode defers the fork until the first X11 client
connects, by which time the event loop is fully running.
The pre-allocated display number is still available immediately so DISPLAY
is set for child processes regardless of whether Xwayland has started.
On shutdown, after SIGKILL-ing the Xwayland process, explicitly remove
/tmp/.X{n}-lock and /tmp/.X11-unix/X{n}. Without this, if the dead
process's PID is reused by another process before the next wmaker run,
wlroots' stale-lock detection (kill(old_pid,0)) wrongly reports the
display as in-use and the next run fails to claim the same display number.
Smoke test updated: check for "XWayland DISPLAY=.*lazy" instead of the
old "XWayland started DISPLAY=" pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In nested mode the wlr_wl_backend creates virtual pointer/keyboard devices
that mirror the host compositor's seat. Wire handle_backend_new_input on
backend->events.new_input (before wlr_backend_start) to capture these real
devices and add separate wl_listener structs for their key/modifier/button/
motion_absolute/frame events. At the end of wl_display_open, switch the
seat to the real keyboard so focus notifications carry the correct keymap
and modifier state from the host.
handle_keyboard_key / handle_keyboard_modifiers updated to use
seat->keyboard_state.keyboard instead of wl_state.keyboard_dev so the
correct modifier state is used regardless of which keyboard fired the event.
Add a grey wlr_scene_rect root background in handle_backend_new_output so
the compositor output shows a proper desktop instead of solid black. The
rect is inserted first in the scene tree (always below client surfaces).
Pointer bounds clamping in handle_pointer_motion, handle_pointer_motion_-
absolute, wl_pointer_warp and wl_pointer_warp_to updated to use actual
output dimensions instead of hardcoded 1920x1079.
All 6 new wl_listener nodes removed in wl_display_close. Smoke test
22/22 PASS, exit 0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WMAKER_WAYLAND_NESTED=0 no longer forces headless when WAYLAND_DISPLAY
is set (auto-detect wins), so the mode check now accepts either
"headless compositor mode" or "nested Wayland compositor mode" and
reports whichever was logged. Remove the now-useless NESTED=0 override
from the launch line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously nested compositor mode required WMAKER_WAYLAND_NESTED=1 to
be set explicitly. Now wl_display_open() checks WAYLAND_DISPLAY: if it
is set the process is already running inside a Wayland session, so
wlr_wl_backend is used automatically and wmaker appears as a window on
the host desktop.
WMAKER_WAYLAND_NESTED=0 overrides the auto-detection and forces headless
mode (used by the smoke test to avoid accidentally going nested when
WAYLAND_DISPLAY is set in the test environment).
Smoke test: add WMAKER_WAYLAND_NESTED=0 to the wmaker launch line so
the headless path is always exercised regardless of the environment.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Three bugs in the shutdown path caused wmaker to exit with SIGABRT (134)
or hang on SIGTERM when running the Wayland backend:
1. wl_display_destroy() SIGABRT — wlroots 0.15 double-remove bug
wlroots registers internal listeners on the XWayland wl_client's destroy
signal. Those listeners self-remove during client destruction.
wlr_xwayland_server_destroy() then tries wl_list_remove() on the same
already-nulled links, corrupting the heap → SIGABRT.
Fix: skip wl_display_destroy() entirely; Exit() calls exit(0) immediately
after wl_display_close() returns, so the kernel reclaims all resources.
2. wl_display_destroy_clients() hang
wlroots' XWayland client destroy handler performs blocking cleanup
that can hang when Xwayland is in mid-dispatch.
Fix: skip wl_display_destroy_clients() for the same reason as (1).
3. Global listener leak — dangling wl_listener.link pointers
All globally-registered wl_listeners (output frame, backend new_output,
xdg_shell new_surface, decoration, xwayland new_surface, keyboard key/
modifiers, pointer motion/button/frame) were never removed before the
owning wlroots objects were freed. This left link.prev/next dangling
into freed signal structs, causing heap corruption in later allocations.
Fix: WL_LISTENER_REMOVE() all 11 global listeners in wl_display_close()
before any display teardown.
Additional fixes included in this phase:
- handle_toplevel_destroy: remove all 10 per-view listeners (was only 4),
preventing dangling pointers after each client session.
- handle_output_frame: guard on wl_state.allocator; call
wlr_scene_output_send_frame_done() when no GPU allocator so frame
callbacks still fire (fixes weston-simple-shm stall on headless output).
- handle_backend_new_output: call wlr_output_schedule_done() after a
failed commit so weston-info does not hang waiting for wl_output.done.
- XWayland shutdown: SIGKILL Xwayland before display teardown to prevent
waitpid() hanging; null server->client to skip double-free in
wlr_xwayland_server_destroy().
- Self-pipe shutdown: write() to a pipe from the SIGTERM signal handler;
event loop reads it and calls wl_display_terminate() from loop context,
avoiding async-signal-safety issues of calling display APIs from a handler.
Smoke test result: 22/22 checks PASS, wmaker exits cleanly (status 0).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When the Wayland backend is active dpy is NULL; all X11 drawing calls
in framewin.c (XMapRaised, XSetWindowBackground, XSetWindowBorder,
XSetWindowBorderWidth, XSetWindowBackgroundPixmap, XClearWindow,
XDrawLine) must be skipped to prevent SIGSEGV when managing XWayland
or synthetic frame windows.
Changes:
- allocFrameBorderPixel: early return when !dpy (no X11 colormap)
- wFrameWindowUpdateBorders: guard XSetWindowBorderWidth and
XSetWindowBorder blocks; guard XMapRaised/XSetWindowBackground in
button-creation branches; guard XMapWindow/XLowerWindow for resizebar
- wFrameWindowChangeState: guard XSetWindowBorder block
- wFrameWindowHideButton: guard XUnmapWindow calls
- wFrameWindowShowButton: guard XMapWindow and XClearWindow calls
- updateTexture: early return when !dpy
- remakeTexture: guard resizebar XSetWindowBackgroundPixmap/XClearWindow
- wFrameWindowPaint: guard entire resizebar XDrawLine section
Frame window structure (wCoreCreate, descriptor setup, stacking) still
runs normally; only the visual rendering is skipped in Wayland mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add check 4h-b: grep for "scene output created" to confirm that
handle_backend_new_output found a valid wl_state.scene when it fired
(the Phase 18 ordering fix). Without the fix the scene was NULL and
scene_output was never created, leaving the nested window invisible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
handle_backend_new_output fires during wlr_backend_start (the nested
Wayland backend triggers a host compositor roundtrip that delivers the
new_output event synchronously). The scene graph and output layout
were previously created *after* wlr_backend_start, so scene_output was
never created, no buffer was ever committed to the host surface, and the
nested window remained invisible.
Move wlr_output_layout_create() and wlr_scene_create() to just before
the backend_new_output listener is wired, ensuring handle_backend_new_output
finds a valid wl_state.scene and can create the scene_output on the spot.
Smoke test: 9/9 checks pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two major additions:
1. Nested Wayland mode (WMAKER_WAYLAND_NESTED=1):
wlr_multi_backend wraps wlr_wl_backend (visual output as a host
Wayland window) and wlr_headless_backend (for synthetic input
devices). wlr_wl_output_create queues the output window before
start; handle_backend_new_output initialises the render pipeline
(wlr_output_init_render, preferred mode, enable, commit) and sets
the window title to "Window Maker".
2. wlr_scene compositing (both modes):
wlr_scene_create() produces a scene graph; per-surface scene nodes
are added in handle_toplevel_map (wlr_scene_xdg_surface_create) and
handle_xwayland_map (wlr_scene_subsurface_tree_create). Positions
are kept in sync via wlr_scene_node_set_position from wl_window_move
and wl_client_synth_configure. handle_output_frame calls
wlr_scene_output_commit on every output vsync to render and send
frame callbacks.
Other changes:
- wlr_allocator_autocreate added; wlr_output_init_render called in
handle_backend_new_output for proper buffer allocation
- headless_inner / wl_backend_inner stored separately so
wlr_headless_add_input_device always uses the headless sub-backend
- frame_timer_cb skips wlr_output_send_frame in nested mode (host
drives frame schedule via output->events.frame)
- Scene nodes removed on surface destroy; allocator/scene torn down
in wl_display_close
Smoke test: new "scene check" verifies "scene graph created" and
"headless compositor mode" log lines.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add wlr_xwayland to the headless compositor so X11 applications
(e.g. GNUstep apps) can run under the Wayland backend.
Key changes:
- Include <wlr/xwayland.h>; add xwayland/xwayland_new_surface_listener
to wl_compositor_state
- Add xw_surface field to wl_toplevel_view so XDG and XWayland windows
share the same view struct and context-store keyed by WNativeWindow
- XWayland window_id used directly as WNativeWindow (no conflict with
XDG synthetic IDs ≥ 0x10000000)
- handle_xwayland_{map,unmap,destroy,set_class}: same wManageWindow/
wUnmanageWindow lifecycle as XDG toplevels
- override_redirect windows (menus, tooltips) are skipped
- view_surface_at: bounds-check path for XWayland surfaces alongside
wlr_xdg_surface_surface_at for XDG toplevels
- vtable functions updated: focus_set, prop_get_normal_hints,
prop_get_wm_class, window_fetch_name, window_get_icon_name,
window_get_client_attrs, client_synth_configure, window_resize,
window_move, client_close, window_get_rect, prop_get_transient_for,
client_get_pid all handle xw_surface != NULL
- wlr_xwayland_create(lazy=false) called in wl_display_open after
compositor and seat; DISPLAY env var set from display_name
- wlr_xwayland_destroy in wl_display_close
- Smoke test: new check 4g verifies "XWayland started DISPLAY=:<n>" log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire xdg-toplevel event listeners for the remaining lifecycle events:
- request_maximize: toggles wMaximizeWindow / wUnmaximizeWindow
- request_fullscreen: calls wFullscreenWindow
- request_minimize: calls wIconifyWindow
- set_app_id: updates wwin->wm_class when the app changes its app_id
Include actions.h and xinerama.h for the WM action function declarations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire handle_toplevel_request_move and handle_toplevel_request_resize
listeners to each xdg_toplevel so clients that initiate CSD drag
operations (e.g. GTK titlebar drag) receive an ack rather than hanging.
In the headless compositor both handlers are currently no-ops; a real
compositor would start a pointer grab here.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire handle_pointer_motion, handle_pointer_motion_absolute,
handle_pointer_button, and handle_pointer_frame listeners to the
headless pointer device. Motion handlers update the virtual pointer
position and call wl_update_pointer_focus; button handler forwards
button state to wlr_seat_pointer_notify_button. This completes the
input event pipeline so a real pointer device added later will
dispatch events to focused Wayland clients automatically.
Smoke test check 4f2 verifies "pointer motion/button/frame listeners
wired" is logged at startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement view_surface_at() and wl_update_pointer_focus() to deliver
wl_pointer.enter/motion/frame events to the Wayland surface under the
virtual pointer. wl_client_synth_configure now records the WM-assigned
position in view->x/y so hit-testing works correctly. After
wManageWindow succeeds, the virtual pointer is warped to the window
centre to immediately deliver pointer focus.
Smoke test check 16c verifies "pointer focus entered surface" is logged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
doAppBounce calls XMoveWindow which crashes when dpy is NULL. Add
early return so urgent-window bounce animations are silently skipped
in Wayland mode (no X11 icon window to move).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set noclip=1 and nodock=1 in wl_screen_create_internals so wWorkspaceNew
does not call wDockCreate (which requires X11 GCs, pixmaps, and windows
unavailable when dpy is NULL). Smoke test check 4f verifies the log.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add !dpy guards to update_icon_pixmap, wIconPaint, wAppIconPaint,
paint_app_icon, and wAppIconMove so the app-icon creation path
(wApplicationCreate → create_appicon_for_application → wIconUpdate)
does not crash when dpy is NULL in Wayland mode. X11 pixmap and
window-drawing operations are silently skipped; icon state is still
tracked for future Wayland rendering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add wWaylandKeyPress(keycode, modifiers) to event.c/h: scans
wKeyBindings and dispatches a subset of WM shortcuts (miniaturize,
hide, maximize, raise/lower, close, focus-next/prev, workspace switch)
using raw XKB keycode + modifier values instead of an XEvent.
In wl_backend.c, wire handle_keyboard_key and handle_keyboard_modifiers
listeners on the headless keyboard device so that:
- key events are checked against WM shortcuts first
- non-shortcut keys are forwarded to the focused client via
wlr_seat_keyboard_notify_key
- modifier changes are always notified via wlr_seat_keyboard_notify_modifiers
The headless backend doesn't generate real key events so the listener
is a no-op in the smoke test, but the infrastructure is ready for a
real DRM+libinput backend.
Smoke test: check for "keyboard key+modifiers listeners wired" log.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set a default XKB keymap on the headless keyboard device in
wl_display_open() so the keyboard has a keycode↔keysym mapping
available from startup.
Implement wl_keysym_to_keycode() by scanning the XKB keymap for the
first keycode that produces the given keysym; XKB keycodes equal
evdev+8 == X11 keycodes, so wKeyBindings are directly compatible.
Implement wl_keysym_from_keycode() as the reverse lookup.
Call wSetModifierMasks(Mod1Mask, Mod1Mask, Mod4Mask, 0) from
wl_screen_create_internals() so AltMask/MetaMask/SuperMask are
non-zero in Wayland mode, making modifier parsing in wXModifierFromKey
produce correct values.
Add wSetModifierMasks() to xmodifier.c/h as the non-X11 entry point.
Replace the four XKeysymToKeycode(dpy, ksym) call sites in
defaults.c, rootmenu.c, and usermenu.c with
wm_backend->keysym_to_keycode() so they work when dpy==NULL.
Smoke test: check for "XKB keymap set on headless keyboard" log line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wSwitchPanelSelectNext, wSwitchPanelSelectFirst, and
wSwitchPanelHandleEvent all access wwin->frame->title to display
the window title in the Alt-Tab switcher panel.
Fall back to wwin->wm_class when frame is NULL so Wayland windows
appear in the switcher without crashing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
raiseWindow: skip stacking operations when wwin->frame is NULL
(Wayland windows have no X11 frame window to restack).
change_focus_and_raise: skip fullscreen stacking-level restore when
oldFocused->frame is NULL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wWindowConfigure: add early-return Wayland path that dispatches
client_synth_configure via vtable and updates client geometry instead
of trying to configure a non-existent X11 frame window.
wWindowSingleFocus: use client dimensions when frame is NULL for the
wScreenBringInside call.
wWindowSaveState: fall back to client width/height when frame is NULL
(non-maximized path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard wNETFrameExtents (returns early when !dpy || !frame), so it
does not try to set X11 properties or dereference titlebar/resizebar
for Wayland windows.
Guard OpenWindowMenu, execWindowOptionCommand, and updateOptionsMenu
in winmenu.c so that the window menu and its KeepOnTop/KeepAtBottom
stacking callbacks do not crash when wwin->frame is NULL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard all keyboard shortcut handlers in event.c that dereference
wwin->frame->core for stacking operations (WKBD_KEEP_ON_TOP,
WKBD_KEEP_AT_BOTTOM, WKBD_RAISE, WKBD_LOWER, WKBD_RAISELOWER,
WKBD_WINDOWMENU, WKBD_XKB_TOGGLE) so they skip cleanly when a
Wayland window is focused.
Add early-return guards to wKeyboardMoveResizeWindow,
wMouseMoveWindow, and wMouseResizeWindow in moveres.c so that
Wayland windows (frame==NULL) do not crash if those entry points
are reached.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard the remaining wwin->frame dereferences that are reachable for
Wayland windows:
- wSetFocusTo: skip ChangeStackingLevel for fullscreen if !frame
- set_window_coords: fall back to client size when frame is NULL
(called by find_Maximus_geometry on neighbour windows)
- wFullscreenMonitorsWindow: early return if !frame
- wMakeWindowVisible: skip wRaiseFrame if !frame
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add if (!wwin->frame) return guards to wShadeWindow, wUnshadeWindow,
wMaximizeWindow, wUnmaximizeWindow, wFullscreenWindow, wIconifyWindow,
and wDeiconifyWindow so that Wayland-managed windows (frame==NULL) do
not crash when these actions are triggered via menu or keyboard shortcut.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add WIN_WORKSPACE(w) macro to window.h that returns frame->workspace
when the window has a frame (X11 path) or wwin->workspace when it does
not (Wayland path, set in Phase 67).
Replace all unsafe frame->workspace reads with WIN_WORKSPACE() in:
- window.c: wWindowObscuresWindow (workspace comparison + geometry),
wWindowFocusPrev/Next (same-workspace filter),
wWindowSaveState (state serialization)
- session.c: session save (workspace name lookup)
- switchmenu.c: all five switch-menu workspace comparisons/lookups
Also guard wWindowObscuresWindow's frame->core width/height reads with
a ternary that falls back to client.{width,height} for frameless windows.
This eliminates the last class of NULL frame->workspace dereferences
reachable from workspace-switch, focus-cycle, session-save, and
switch-menu code paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>