Commit Graph

51 Commits

Author SHA1 Message Date
Levi Neely 5dd9078c49 wayland: Phase 29+ — signalfd-based SIGTERM handling for nested mode
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>
2026-03-31 14:08:00 +02:00
Levi Neely 8fe783e304 wayland: Phase 30 — native Wayland dock panel via wm_shm_surface
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>
2026-03-31 13:55:24 +02:00
Levi Neely 7018dac550 wayland: revert XWayland bridge (Path 1); clean native Wayland baseline
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>
2026-03-31 12:51:08 +02:00
Levi Neely aac488641d wayland: Phases 26-28 — subcompositor, primary selection, xdg-output, idle-inhibit
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>
2026-03-30 17:13:03 +02:00
Levi Neely 52c6ef9326 wayland: Phases 24+25 — click-to-focus, client cursor surface, clipboard
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>
2026-03-30 17:08:17 +02:00
Levi Neely aee57de37d wayland: Phase 23 — cursor image rendering via wlr_cursor + xcursor theme
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>
2026-03-30 17:03:36 +02:00
Levi Neely 73cdf7a6b3 wayland: Phase 22 — interactive move/resize grab
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>
2026-03-30 16:57:38 +02:00
Levi Neely 632c093355 wayland: Phase 21 — lazy XWayland + lock file cleanup on shutdown
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>
2026-03-30 13:05:40 +02:00
Levi Neely 177e13f995 wayland: smoke test — accept headless or nested compositor mode
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>
2026-03-30 11:28:17 +02:00
Levi Neely 9104a1944f wayland: auto-detect nested mode from WAYLAND_DISPLAY
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>
2026-03-30 11:23:47 +02:00
Levi Neely 8c211e1a33 wayland: smoke test — verify scene_output creation in new_output handler
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>
2026-03-28 21:50:23 +01:00
Levi Neely f4cc8a803e wayland: Phase 18 — nested compositor + wlr_scene rendering
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>
2026-03-28 21:37:30 +01:00
Levi Neely 5b1590607c wayland: Phase 17 — XWayland support for X11 client windows
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>
2026-03-28 21:24:49 +01:00
Levi Neely b9b3b2de57 wayland: Phase 14 — pointer motion/button/frame event pipeline
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>
2026-03-28 21:05:39 +01:00
Levi Neely fae9360af5 wayland: Phase 13 — pointer focus delivery to Wayland surfaces
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>
2026-03-28 21:03:00 +01:00
Levi Neely 6fb9413cbf wayland: Phase 11 — disable dock/clip in Wayland mode; guard workspace creation
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>
2026-03-28 20:52:28 +01:00
Levi Neely b576025f9b wayland: Phase 78 — keyboard event dispatch and WM shortcut handler
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>
2026-03-28 20:36:32 +01:00
Levi Neely b1e4a99c5d wayland: Phase 77 — XKB keymap + keysym_to_keycode + modifier masks
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>
2026-03-28 20:32:07 +01:00
Levi Neely c7acf9d89d wayland: Phase 76 — frame NULL guard in switchpanel.c title display
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>
2026-03-28 10:01:45 +01:00
Levi Neely 2c7407a6c8 wayland: Phase 75 — frame NULL guards in cycling.c
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>
2026-03-28 10:00:02 +01:00
Levi Neely b44d556351 wayland: Phase 74 — frame NULL guards in window.c
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>
2026-03-28 09:58:43 +01:00
Levi Neely f772c79ff5 wayland: Phase 73 — frame NULL guards in wmspec.c and winmenu.c
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>
2026-03-28 09:56:44 +01:00
Levi Neely b1cc22ed54 wayland: Phase 72 — frame NULL guards in event.c and moveres.c
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>
2026-03-28 09:55:05 +01:00
Levi Neely 7734929cd5 wayland: Phase 71 — early-return guards in major action functions
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>
2026-03-28 09:50:02 +01:00
Levi Neely c2c4f699f3 wayland: Phase 70 — apply WIN_WORKSPACE macro to all frame->workspace reads
Mechanically replace all remaining wwin->frame->workspace reads (41
occurrences across 9 source files) with the WIN_WORKSPACE(w) macro
introduced in Phase 69.  Write sites (inside if-dpy or if-frame guards)
retain direct frame->workspace assignments as they cannot reach Wayland.

Also fixes two sed-introduced malformations:
  - actions.c: tmp->WIN_WORKSPACE(prev) → WIN_WORKSPACE(tmp->prev)
  - placement.c: ((WIcon*)parent)->WIN_WORKSPACE(owner) → correct form

Files changed: actions.c cycling.c event.c moveres.c placement.c
               switchpanel.c window.c wmspec.c workspace.c

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:46:35 +01:00
Levi Neely 36aa1c5725 wayland: Phase 69 — WIN_WORKSPACE macro; safe workspace reads without frame
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>
2026-03-28 09:43:09 +01:00
Levi Neely 2c55725bb6 wayland: Phase 68 — placement.c: safe overlap calculation without frame
Guard all frame->core and frame->workspace dereferences in placement.c
against NULL frame pointers (Wayland windows have no WFrameWindow):

- calcSumOfCoveredAreas: skip windows without frame (no stacking level
  or geometry available via frame; Wayland window client geometry is
  tracked separately in wwin->client.* and wwin->frame_x/y).

- window_overlaps: add early-return path for frameless windows that uses
  wwin->client.{width,height} and wwin->frame_x/y for the overlap test,
  bypassing the frame->core stacking level check.

- getPlaceOrigin (icon-placement helper): guard frame->workspace reads
  for WCLASS_MINIWINDOW and WCLASS_WINDOW parent types using logical-OR
  with the wwin->workspace fallback.

Together these prevent NULL dereferences when smart/auto/cascade
placement iterates the managed-window list and encounters Wayland
windows alongside X11 windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:40:44 +01:00
Levi Neely e60dff8ffd wayland: Phase 67 — WWindow.workspace; safe workspace switching without frame
Add int workspace to WWindow so Wayland-managed windows (which have no
WFrameWindow and therefore no frame->workspace slot) can track their
workspace assignment.  Set wwin->workspace = workspace in the Wayland
branch of wManageWindow alongside the existing geometry recording.

Guard wWorkspaceForceChange against NULL frame dereferences: introduce a
tmp_workspace local that falls back to tmp->workspace when frame is NULL,
and protect the omnipresent workspace assignment the same way.  Also add
a NULL guard for tmp->icon->core in the miniwindow-unmap path.

This prevents NULL dereferences if workspace switching is triggered
(e.g., via a root-menu action) while Wayland windows are managed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:38:07 +01:00
Levi Neely a2571be53e wayland: Phase 66 — XDG popup initial configure for context menus
Handle WLR_XDG_SURFACE_ROLE_POPUP surfaces in handle_xdg_new_surface by
scheduling the initial configure event.  XDG popups (context menus,
autocomplete dropdowns, tooltips) need a compositor configure before they
can commit their first buffer; without it they stall indefinitely.

Popups are not managed by the policy layer — they are self-contained
transient surfaces positioned relative to their parent by the positioner.
We schedule configure and log the event without creating a wl_toplevel_view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:33:22 +01:00
Levi Neely 9bdf3c058b wayland: Phase 65 — prop_get_transient_for via xdg_toplevel->parent
Implement prop_get_transient_for for the Wayland backend by walking the
xdg_surface->toplevel->parent pointer and looking up the corresponding
wl_toplevel_view to return its synthetic WNativeWindow ID.

This ensures dialog windows and other transient toplevels that set a
parent via set_parent() are correctly identified as transients by the
policy layer (wManageWindow sets transient_for / no_miniaturizable /
no_miniaturize_button correctly).

Add a helper wl_find_view_by_xdg_surface() for surface-based lookups.
Smoke test gains a header comment noting Phase 65 is a build-only check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:29:28 +01:00
Levi Neely cc846daeca wayland: Phase 64 — zxdg_decoration_manager_v1; CSD negotiation
Advertise the XDG decoration manager protocol so clients that support
server-side decoration negotiation can communicate with the compositor.
Since the Wayland path has no X11 frame rendering, respond to all
new_toplevel_decoration events with CLIENT_SIDE mode, directing clients
to draw their own title bars and borders.

Smoke test gains two new checks: compositor logs "xdg-decoration-manager
created" at startup, and weston-info reports zxdg_decoration_manager_v1
in the global registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:26:52 +01:00
Levi Neely 36b024d849 wayland: Phase 63 — DELETE_WINDOW=1 for Wayland; wClientSendProtocol dispatches client_close
All XDG toplevels support polite close via wlr_xdg_toplevel_send_close.
PropGetProtocols now sets DELETE_WINDOW=1 in the Wayland path so
wManageWindow sets kill_close=0 (prefer polite close over force-kill)
and the MC_CLOSE menu item is enabled for Wayland windows.

wClientSendProtocol dispatches wm_backend->client_close in the Wayland
path instead of returning early; since all atoms are 0 in Wayland, atom
comparison is unreliable, but all callers that reach wClientSendProtocol
are requesting a polite close — the only Wayland-meaningful WM_PROTOCOL.

wManageWindow succeeded log gains a kill_close= field; smoke test check
21 verifies kill_close=0 for each managed Wayland toplevel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:24:22 +01:00
Levi Neely 6764c14863 wayland: Phase 62 — mapped flag, focus lifecycle, and keyboard clear-focus
Three related fixes to make the WM focus lifecycle work end-to-end in
the Wayland path:

1. window.c: set wwin->flags.mapped=1 for Wayland windows (the
   wWindowMap call in wManageWindow is guarded by if(wwin->frame)
   which is NULL in Wayland, leaving mapped=0 and causing wSetFocusTo
   to take the no-focus branch immediately).

2. screen.c: call screen_create_internals in the Wayland goto path;
   this was skipped by goto screen_done but is needed to set
   auto_focus=1 and clear initial keyboard focus via wlr_seat.

3. wl_backend.c: implement wl_screen_create_internals (sets
   no_focus_win=WNATIVE_NONE, auto_focus=1, clears seat focus);
   wl_focus_set now calls wlr_seat_keyboard_clear_focus and logs
   "focus_clear" when win==WNATIVE_NONE; remove the redundant direct
   wl_focus_set call from handle_toplevel_map since wManageWindow now
   handles focus via wSetFocusTo.

Smoke test: add check 20 verifying "focus_clear" is logged after the
last managed window unmaps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:11:56 +01:00
Levi Neely 54c1a3a181 wayland: Phase 54 — virtual pointer position tracking
Add pointer_x/pointer_y fields to wl_compositor_state, initialised to
screen centre (960,540) in wl_display_open.  pointer_warp/warp_to now
update the tracked position (clamped to output bounds); pointer_query and
pointer_query_full return the tracked coordinates instead of hardcoded 0,0.

Also fix the smoke test to ensure XDG_RUNTIME_DIR is set before launching
wmaker (the fix from Phase 53 was already applied; this commits it
permanently).  Adds a Phase 54 check verifying the "virtual pointer
initialised at" log line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:23:48 +01:00
Levi Neely ec98ed3d21 wayland: Phase 53 — headless pointer device; seat advertises pointer capability
Add a headless pointer input device (WLR_INPUT_DEVICE_POINTER) to the
Wayland seat alongside the existing keyboard device.  wlr_seat now
advertises both WL_SEAT_CAPABILITY_KEYBOARD and WL_SEAT_CAPABILITY_POINTER
to clients that bind wl_seat.

Also fix the smoke test to set XDG_RUNTIME_DIR when it is unset so
wl_display_add_socket_auto() can always create the compositor socket.

Smoke test gains a Phase 53 check: weston-info must show 'pointer' in the
wl_seat capabilities output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:19:01 +01:00
Levi Neely c36bd7384e wayland: Phase 51 — multi-client: two simultaneous toplevels managed cleanly
Run two weston-simple-shm processes in parallel during the smoke test
and verify that all three toplevels (one from the single-client section
plus two from the parallel section) are managed and unmanaged without
crashing the compositor.  The synthetic-ID counter, focus chain, and
wUnmanageWindow path all survive the multi-client scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:06:26 +01:00
Levi Neely 14da9a65ae wayland: Phase 50 — client_get_pid via wl_client_get_credentials; misc guards
Implement wl_client_get_pid using wl_resource_get_client +
wl_client_get_credentials so the WM can retrieve the OS PID of
Wayland clients.  Guard getCommandForWindow in misc.c against NULL
dpy to prevent XGetCommand crash when the real PID path fires.
Guard wWindowChangeWorkspace against NULL frame for frameless
Wayland windows.  Log pid= in the wManageWindow-succeeded line;
add Phase 50 pid check to smoke test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:03:55 +01:00
Levi Neely 69cee6fb15 wayland: Phase 45 — pointer_query returns on-screen; focus path guarded
pointer_query / pointer_query_full: return 1 when compositor is running
(wl_state.started) so the same_screen check in wManageWindow passes and
wSetFocusTo is called for newly mapped XDG toplevels.

Guard frame-dependent calls in the focus update path:
  - wWindowFocus (window.c): guard wFrameWindowChangeState on frame!=NULL
  - wWindowUnfocus (window.c): same; also guard wFrameWindowPaint on
    owner->frame when clearing semi-focus on transient owner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 06:43:16 +01:00
Levi Neely 0fe2960995 wayland: Phase 43 — focus_set delivers keyboard focus via wlr_seat
Create a headless keyboard input device during wl_display_open and
attach it to wlr_seat so wlr_seat_keyboard_notify_enter() can deliver
keyboard focus to XDG toplevel surfaces.

Implement wl_focus_set: look up the view by synthetic ID, call
wlr_seat_keyboard_notify_enter with the xdg_surface->surface.
Call wl_focus_set from handle_toplevel_map immediately after
wManageWindow so newly mapped toplevels receive keyboard focus.

Smoke test: add Phase 43 check — grep "focus_set id=" in stderr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 06:40:16 +01:00
Levi Neely d9a70e9177 wayland: Phase 42 — window_get_client_attrs returns real map/geometry
Implement wl_window_get_client_attrs: look up the toplevel view, return
WCLIENT_VIEWABLE with width/height from wlr_surface.current for mapped
surfaces; WCLIENT_UNMAPPED for unknown IDs.

Extend the wManageWindow success log to include geo=WxH so the smoke
test can verify the geometry is non-zero after the client commits a frame.

Smoke test: add Phase 42 check — grep geo= and verify it is logged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 06:37:59 +01:00
Levi Neely 0ea33dc830 wayland: Phase 41 — prop_get_wm_class/window_fetch_name from XDG metadata
Add wl_find_view_by_id() helper that walks toplevel_list to recover a
wl_toplevel_view from its synthetic WNativeWindow ID.

Implement vtable slots:
  - prop_get_wm_class: returns XDG app_id as both WM_CLASS and WM_INSTANCE
  - window_fetch_name: returns XDG toplevel title

Extend the wManageWindow success log to include class= and name= so
the smoke test can verify these fields are populated from Wayland metadata.

Smoke test: add Phase 41 check — grep for
"class=org.freedesktop.weston.simple-shm" in manage log.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 06:36:22 +01:00
Levi Neely 490ecb34ff wayland: Phase 40 — wire toplevel unmap to wUnmanageWindow
Guard all dpy==NULL / frame==NULL crash sites in the unmanage path:
  - wUnmanageWindow (window.c): frame ptr set to NULL-safe expression;
    window_unmap, raise_delay timer, and owner border change all
    guarded on frame != NULL
  - wSelectWindow (actions.c): early return if !wwin->frame
  - wNETCleanupFrameExtents (wmspec.c): early return if !dpy
  - wClientRestore (client.c): early return if !dpy

Wire handle_toplevel_unmap → context_find → wUnmanageWindow(destroyed=True)
so XDG toplevels are removed from scr->focused_window on client disconnect.

Smoke test: add Phase 40 check — grep "toplevel unmapped".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 06:34:15 +01:00
Levi Neely ca09635129 wayland: Phase 39 — wire toplevel map to wManageWindow
Guard all dpy==NULL crash sites reached from wManageWindow:
  - wNETWMGetPidForWindow (wmspec.c) — XGetWindowProperty with NULL dpy
  - wNETWMCheckClientHints (wmspec.c) — XGetWindowProperty
  - wNETWMCheckInitialClientState (wmspec.c) — XChangeProperty chain
  - getMWMHints (motif.c) — XInternAtom before NULL dpy check
  - wClientSetState (client.c) — XChangeProperty
  - wColormapInstallForWindow (colormap.c) — XGetWindowAttributes
  - wWindowSetKeyGrabs (window.c) — frame->core->window dereference

Wire handle_toplevel_map → wManageWindow(scr, view->id) so mapped
XDG toplevels appear in scr->focused_window.  Log success via
"wl_backend: wManageWindow succeeded id=0x%lx".

Smoke test: add Phase 39 check — grep "wManageWindow succeeded".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 06:23:23 +01:00
Levi Neely 74a43f0180 wayland: Phase 38 — per-toplevel view lifecycle and context store
Introduce struct wl_toplevel_view with map/unmap/destroy wl_signal
listeners and a synthetic WNativeWindow ID (>= 0x10000000).
Implement context_save/find/delete as a per-key linked list of
(WNativeWindow, void*) entries — enables downstream policy code to
look up views by handle.  wl-smoke-test now verifies "toplevel mapped"
appears in compositor stderr when weston-simple-shm creates its surface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:19:18 +01:00
Levi Neely 3e1fd061e9 wayland: Phase 37 — frame timer drives client frame callbacks at 60 fps
Replace the output frame listener + render pipeline approach (which
required a swapchain allocator) with a simple wl_event_loop timer that
calls wlr_output_send_frame every 16 ms.  Clients (weston-simple-shm)
now complete their render loops without the compositor needing a GPU
allocator.  Also schedule an initial xdg_surface configure on new
toplevels so they can commit their first buffer.  wl-smoke-test verifies
weston-simple-shm runs for 3 s without crashing the compositor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:15:59 +01:00
Levi Neely 2182ec0fc1 wayland: Phase 36 — wl_shm, wl_output, XDG toplevel listener
Advertise wl_shm (via wlr_renderer_init_wl_shm) and expose the headless
output as a wl_output global so SHM buffer clients can render.  Wire up
the xdg_shell new_surface listener to track incoming XDG toplevels
(management hook placeholder).  wl-smoke-test now verifies wl_shm and
wl_output in the registry and that weston-simple-shm can connect without
crashing the compositor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:12:21 +01:00
Levi Neely 075b648021 wayland: Phase 35 — advertise core Wayland protocol globals
Register wl_compositor (+ wl_subcompositor), wl_seat, wl_data_device_manager,
and xdg_wm_base in the compositor's global registry so that Wayland clients
can enumerate and bind them.  Add generated xdg-shell-protocol.{h,c} from
wayland-scanner.  wl-smoke-test now uses weston-info to verify all three
key globals are present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:09:39 +01:00
Levi Neely 97a9209b4f wayland: Phase 34 — Wayland socket, output layout, real monitors_query
Create the compositor UNIX socket via wl_display_add_socket_auto so
Wayland clients can connect; store socket name and set WAYLAND_DISPLAY.
Add wlr_output_layout for multi-head tracking and implement monitors_query
to return real 1920x1080 head geometry from the headless output.
wl-smoke-test now verifies the socket file exists in XDG_RUNTIME_DIR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:06:49 +01:00
Levi Neely f7dfc50f26 wayland: Phase 33 — event_loop_run/atom_intern vtable; wmaker starts and exits cleanly under Wayland
Add display_default_screen, atom_intern, and event_loop_run vtable slots.
Guard all X11-only paths in startup.c/screen.c/shutdown.c/event.c/xdnd.c
with if (!dpy) so the Wayland path reaches wl_display_run() and exits
cleanly (exit 0) on SIGTERM.  wl-smoke-test.sh now PASSES.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:02:26 +01:00
Levi Neely e8e230b2db wayland: add WMAKER_USE_WAYLAND backend selector and split smoke tests
- startup.c: include wl_backend.h when USE_WAYLAND_BACKEND is defined;
  select wl_backend when WMAKER_USE_WAYLAND env var is set, otherwise
  fall back to x11_backend (no-op for non-Wayland builds)
- tests/smoke-test.sh renamed to tests/x11-smoke-test.sh (X11-only path)
- tests/wl-smoke-test.sh: new Wayland smoke test; currently verifies
  wl_backend symbol presence and wlroots linkage; headless compositor
  tests will be added as vtable slots are implemented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 22:29:27 +01:00