# WindowMaker Wayland Port — Roadmap ## Current State (2026-05-28, final) The backend abstraction is complete. The `src/` policy layer has zero direct Xlib calls and zero `_x11_raw` references. The Wayland backend (wlroots-based, 10K lines) implements all core window management: stacking, maximize, shade, workspaces, alt-tab, dock/clip, menus, move/resize, focus, and frame rendering. **WINGs X11 call isolation is complete.** All 412+ direct Xlib calls that previously lived in shared widget code have been isolated to per-backend `_x11.c` files with Wayland stubs in `wstubs_no_x11.c`. The remaining work is feature implementation, not leak cleanup. **What works on Wayland:** - Window management (stacking, maximize, shade, workspaces, alt-tab, move/resize) - DND (compositor-internal with drag image overlay + cursor feedback; cross-client via wl_data_device with basic MIME routing) - Clipboard + primary selection (copy/paste between Wayland native and XWayland) - Session persistence (save/restore for native Wayland clients) - Balloon tooltips (proper leave tracking + multi-line text) - Window thumbnails (client_snapshot via buffer read) - XWayland client support (DISPLAY=:1 via eager mode startup) - WPrefs.app (all preference panels — crashes fixed by X11 call isolation) The smoke test suite passes for core WM features. What remains is **feature completion and production hardening**. --- ## Priority 1: Daily-Driver Gaps These block using the Wayland backend as a primary desktop. ### 1.1 Session Persistence `src/session.c` (614 lines) — verify save/restore works on Wayland. Window positions are tracked in `WWindow` (backend-neutral). The only concern is matching restored sessions to native XDG clients via `app_id` instead of `WM_CLASS`. - [x] Save/restore state for Wayland native clients: `PropGetWMClass` already returns the XDG `app_id` on Wayland. Session save no longer bails when `GetCommandForWindow` returns NULL (external Wayland clients have no ICCCM command). Restore matches by `app_id` via instance+class without requiring a command. - [x] Save/restore works for Wayland native clients (foot). XWayland (xterm) needs WM_COMMAND matching — lower priority. - [ ] Verify dock state persistence (icon layout in `~/GNUstep/Defaults/`) ### 1.2 Multi-Monitor Currently single-output only. wlroots provides `wlr_output_layout` and `new_output` signal. - [ ] Populate `WScreen.heads[]` from `wlr_output_layout_get_box` in `screen_open` - [ ] Handle output hotplug (migrate windows on disconnect) - [ ] Per-head usable area calculation (dock margins) - [ ] Test with nested multi-output (`WMAKER_WAYLAND_NESTED=1` + `wlr-randr`) ### 1.3 Clipboard wlroots advertises `wl_data_device_manager` and `zwp_primary_selection_v1`. XWayland bridge auto-syncs. Needs verification. - [x] Copy/paste between native Wayland clients — works - [x] Copy/paste between XWayland and native clients — works - [x] Primary selection (middle-click paste) — works (both directions) ### 1.4 Remaining Vtable Stubs | Slot | Status | |------|--------| | `overlay_draw_*` | ✅ Done — real pixman overlay implementation | | `client_snapshot` | ✅ Done — buffer read for native, XGetImage for XWayland | | `window_set_border` | ✅ No-op (borders drawn into frame buffer) | | `client_synth_configure` | ✅ Done — XDG configure + XWayland configure | | `pointer_change_grab` | ✅ Done — sets cursor + establishes grab state during move/resize (fix: was not setting grab_type for opaque moves, causing cursor to reset to arrow on every motion event) | --- ## Priority 2: WPrefs.app Full Port ### Status: Direct X11 Calls Resolved All 11 direct Xlib calls previously reported in WPrefs.app have been audited: | File | Previously Reported | Actual Status | |------|-------------------|--------------| | `TexturePanel.c` | 7 calls | **0 active** — all inside `#if 0` dead code blocks | | `Expert.c` | 1 (XGetDefault) | **0 active** — call already removed | | `MouseSettings.c` | 1 (XChangePointerControl) | **0 active** — call already removed | | `xmodifier.c` | 1 (XGetModifierMapping) | **0 active** — only in a comment | WPrefs.app itself now has **zero direct Xlib calls**. The crash source is in WINGs widget library, which WPrefs links against. ### Root Cause: WINGs X11 Leaks (412 calls) WINGs still has **412 direct Xlib calls** across 15 non-split widget files that bypass the 5-vtable dispatch (`W_ViewBackend`, `W_EventBackend`, `W_RenderBackend`, `W_FontBackend`, `W_ColorBackend`). These are the crash sites when WPrefs calls WINGs APIs on the Wayland path. | WINGs File | X11 Calls | What It Does | |------------|-----------|-------------| | `wcolorpanel.c` | 89 | Pixmap manipulation, `XGetImage`, pointer grabs, cursor recoloring | | `dragsource.c` | 45 | XDND protocol (`XSendEvent`, `XInternAtom`, grabs) | | `wwindow.c` | 41 | ICCCM window properties (`XSetWMName`, `XSetWMHints`, etc.) | | `winputmethod.c` | 40 | XIM/XIC lifecycle (`XOpenIM`, `XCreateIC`, `XLookupString`) | | `dragdestination.c` | 37 | XDND protocol (receive side) | | `widgets.c` | 35 | Screen init, GC/cursor creation, modifier mapping | | `wballoon.c` | 28 | Tooltip drawing, shape extension | | `wtext.c` | 23 | Clipboard, grabs | | `selection.c` | 21 | X selection protocol | | `dragcommon.c` | 16 | XDND shared atom/property helpers | | `wappresource.c` | 15 | App icon property reading | | `wtextfield.c` | 10 | Clipboard, grabs | | `wpopupbutton.c` | 7 | Pointer grabs | | `wruler.c` | 3 | `XRectangle` type usage | | `wmisc.c` | 2 | Coordinate translation | These files need backend abstraction — either routing through the existing 5 vtables, or adding new vtables for DND, selection, and input methods. ### WINGs X11 Call Isolation — COMPLETE All 412+ direct Xlib calls in WINGs have been isolated to `_x11.c` backend files. No active X11 calls remain in non-backend WINGs source files. | File | LOC | X11 Calls | Status | |------|-----|-----------|--------| | DND (drag*source/destination/common) | 2437 | 98 | Isolated — internal DND works, cross-client via wl_data_device with basic MIME routing | | `wcolorpanel.c` | 3556 | 89 | Split into `wcolorpanel.c` + `_x11.c` + `_wl.c` | | `wwindow.c` + `widgets.c` | 1849 | 76 | Already clean — all via vtable dispatch | | `winputmethod_x11.c` | 243 | 40 | Isolated — Wayland stub in wstubs_no_x11.c | | `selection_x11.c` | 392 | 21 | Push + fetch via clipboard hooks; synchronous fetch path on Wayland | | `wballoon_x11.c` | 476 | 28 | Wayland balloon_draw in wview_wl.c renders yellow tooltip with multi-line support | | `wtext.c` + `wtextfield.c` | 5577 | 33 | Already clean — all via vtable dispatch | | All other `_x11.c` files | ~2000 | ~27 | Isolated — stubs in wstubs_no_x11.c | --- ## Priority 3: Visual Completeness ### 3.1 Iconify/Deiconify Zoom Animation The X11 path uses XOR GC rubber-band rectangles (`XGrabServer` + `XDrawRectangle`). On Wayland, implement as per-frame pixman overlay buffer. - [ ] Implement `overlay_draw_rect` in wl_backend.c using a transient `wlr_scene_buffer` - [ ] Wire `animateResize()` in actions.c to use the vtable ### 3.2 Client Snapshot (Window Thumbnails) Used by switch panel and icon preview. X11 uses `XGetImage`. Wayland options: - Render last committed `wlr_texture` into an RImage via pixman blit - Use `wlr_screencopy_manager_v1` - [ ] Implement `client_snapshot` vtable slot using last-committed texture ### 3.3 Window Inspector `src/winspector.c` — WINGs-based dialog. Should work on Wayland since WINGs widgets are ported. Needs testing. - [ ] Test window inspector opens and displays attributes - [ ] Test attribute changes apply correctly --- ## Priority 4: HiDPI & Scaling - [ ] Set `wlr_output_set_scale` per output - [ ] Multiply pixman buffer allocations by scale factor - [ ] Set PangoFT2 DPI from `output.scale * 96.0` - [ ] Ensure `WScreen.width/height` remain logical coordinates - [ ] Test at 2x scale --- ## Priority 5: Protocol Wiring wlroots already advertises these protocols. Wire them to policy: | Protocol | Purpose | Effort | |----------|---------|--------| | `ext-session-lock-v1` | Screen lock | Medium | | `xdg_activation_v1` | Focus stealing prevention | Low | | `zwp_idle_inhibit_manager_v1` | Prevent blank during video | Low | | `ext-foreign-toplevel-list-v1` | Taskbar integration | Low | | `zwp_input_method_v2` | IME support | Medium | | `wlr_gamma_control_v1` | Night light | Low | --- ## Priority 6: DRM/KMS Native Backend For running without a host compositor: - [ ] Use `wlr_backend_autocreate` (replaces headless/nested) - [ ] libinput for real hardware input (wlroots handles this) - [ ] VT switching via `wlr_session` - [ ] Test on real hardware --- ## Architecture Notes - **Rendering pattern**: pixman_image_t → wlr_scene_buffer → scene graph position - **Event flow**: wlroots signals → wl_backend.c handlers → WMEvent → DispatchEvent - **WINGs on Wayland**: compositor-internal views (not wl_surfaces), pixman-rendered, events via ring buffer from compositor hit-testing - **wl_backend.c (420K) should be split** into wl_frame.c, wl_menu.c, wl_input.c, wl_toplevel.c, wl_output.c as complexity grows --- ## Reference Documents | Document | Purpose | |----------|---------| | `ARCHITECTURE.md` | Codebase structure, data flow, key types | | `BACKEND_ABSTRACTION.md` | Historical analysis of X11 leaks (all resolved) | | `X11_IMPLEMENTATION.md` | How X11 path works (correctness reference) | | `ACTION_PLAN_PROMPTS.md` | Task templates for mechanical porting work | | `tools/wm-port/` | Agent-assisted task dispatch tooling |