windowmaker-wl/WAYLAND_ROADMAP.md

50 KiB
Raw Blame History

WindowMaker Wayland Port — Roadmap

Last updated: 2026-05-18

Governing Principles

  1. No guards in the middle — the core (src/, wrlib/, WINGs/) never asks "am I on X11 or Wayland?" Backend-specific behavior lives exclusively in the backend that owns it. Zero #ifdef WAYLAND or runtime backend checks in shared code.

  2. Duplication over coupling — if both backends need similar-but-not-identical logic, each gets its own copy. Shared abstractions that leak backend details are forbidden. Clarity and independence beat DRY.

  3. Fail fast, fail loudly — no defensive null checks, no silent fallbacks, no "just in case" guards. If a precondition is violated, crash immediately with a clear signal. Defensive coding for the sake of being defensive is not acceptable.

  4. Perfect architecture over fewer lines — the right structure wins even if it means more files or duplicated code. Clean separation now buys simple reasoning forever.

┌─────────────┐     ┌─────────────────────────┐     ┌─────────────────┐
│ X11 Backend │◄────┤  Core / wrlib / WINGs   ├────►│ Wayland Backend │
└─────────────┘     └─────────────────────────┘     └─────────────────┘
        │                       │                           │
  backend-specific        pure policy,              backend-specific
  special cases           zero backend guards       special cases

Architecture

WindowMaker 0.96.0 uses a vtable-based backend abstraction (WMBackend struct in src/backend/backend.h) with ~150+ function pointer slots. Policy code calls wm_backend->operation(...) and never touches X11/Wayland APIs directly.

Two backends:

  • X11: src/backend/x11/x11_backend.c (150K)
  • Wayland: src/backend/wayland/wl_backend.c (316K, wlroots-based compositor)

Activation: WMAKER_USE_WAYLAND=1 env var. Nested mode: WMAKER_WAYLAND_NESTED=1.

Three major libraries:

  • src/ — WM core (policy layer, zero Xlib calls remaining)
  • WINGs/ — Widget toolkit ("WINGs Is Not GNUstep"), split into _x11.c / _wl.c backends
  • wrlib/ — Raster graphics library (image loading, manipulation, rendering)

Completed (as of 2026-05-18)

  • Backend vtable abstraction (150+ slots)
  • Policy layer fully X11-free (zero direct Xlib calls in WM core)
  • wlroots compositor (headless + nested modes)
  • XDG shell + XWayland bridge
  • Frame rendering (titlebar, buttons, resizebar) via pixman → wlr_scene_buffer
  • Menu rendering + highlighting + item activation
  • Root menu from config file
  • Dock rendering (SHM panel, icon slots, app tracking)
  • Clip rendering
  • Background rendering
  • Mouse input (motion, buttons, scroll, grabs)
  • Keyboard input (XKB, modifiers, keysym translation)
  • Interactive move/resize with edge detection + cursor changes
  • Click-to-focus via hit-testing
  • Run dialog (WINGs modal loop with compositor tick)
  • Window manage/unmanage lifecycle (XDG toplevel + XWayland)
  • Focus delivery (wlr_seat_keyboard_notify_enter)
  • WINGs Wayland backend (views, fonts/PangoFT2, events/ring buffer, rendering/pixman, colors)
  • Smoke test suite (30+ tests in tests/wl-smoke-test.sh)
  • Alt-Tab window cycling (switch panel)
  • Wave 1 complete: all XEvent* → WMEvent* signature changes (8 tasks: A5.1, A5.2, B.3, E.3, E.4, R.1, R.2, R.3)
  • 7 _x11_raw references eliminated in policy code (src/: 16 → 9 remaining)
  • Crash regression fixed: use-after-free on modal dialog close — W_IsValidView() validates stale _view_target against view_table at fetch boundary in wevent_wl.c; defense-in-depth in wview.c and wevent.c
  • Scene graph cleanup: WINGs toplevel close destroys wlr_scene_node and drops buffer via W_ViewDestroyBackingFn → wl_view_destroy_backing_impl()
  • XEvent synthesis block deleted in cycling.c
  • Wave 2 complete: 4 mechanical elimination tasks (A4.1, B.1, R.5, R.7)
  • C.1 skipped: XOR rubber-band drawing in moveres.c is inside #if 0 — dead code
  • Native XDG client rendering fix: surface_commit_listener calls wlr_output_schedule_frame() on every buffer commit (commit 8d963e7)

Phase A: Core Window Management Gaps

Priority: HIGH — needed for daily-driver use.

# Task Key Files Complexity
A1 Stacking (raise/lower via scene graph) stacking.c, wlr_scene_node_raise/lower ✅ Done
A2 Window maximize actions.c, usable area calc ✅ Done
A3 Window shade (titlebar collapse) actions.c, frame buffer resize ✅ Done
A4 Workspace switching (create/destroy/switch) workspace.c, scene tree reparenting Medium
A5 Alt-Tab window cycling + switch panel cycling.c, switchpanel.c, vtable switchpanel_enter Medium
A6 Window iconification (minimize) + miniwindow icon.c, appicon.c, vtable icon_paint/icon_update_pixmap Done
A7 Window placement algorithms (smart/cascade/random) placement.c (may already work via vtable) Low

Phase B: Dock & App Icons

# Task Key Files Complexity
B1 App icon rendering (load .tiff/.png, render into dock) appicon.c, wrlib loaders, vtable appicon_show Medium
B2 Dock icon drag & drop (reorder, add/remove) dock.c DnD handlers Medium
B3 Dock auto-launch on login dock.c session restore Low
B4 Clip workspace indicator (number, arrows) dock.c clip paint Low
B5 Drawer support (horizontal dock extension) dock.c drawer subsystem Medium
B6 Icon bounce on launch vtable appicon_bounce Low

Phase C: Visual Polish & Animations

# Task Key Files Complexity
C1 Slide animations (iconify/deiconify zoom) vtable slide_window Medium
C2 Menu scrolling (long menus) menu.c scroll logic Low
C3 Balloon tooltips (dock icon hover) vtable balloon_show Low
C4 Cursor themes (xcursor loading) wlr_xcursor_manager Low
C5 Window opacity (NETWM alpha) vtable netwm_set_opacity Low

Phase D: WINGs Widgets (for Preferences & Inspectors)

Core rendering backends exist. Complex widgets need verification/porting:

Widget Needed For Notes
WMList File panels, icon chooser Test
WMBrowser File browser in dialogs Test
WMColorPanel Color picker in prefs May need porting
WMFontPanel Font selection May need porting
WMFilePanel Open/save dialogs Test
WMTabView Preferences app tabs Test
WMScroller/WMScrollView Scrollable content Test
WMSlider Preference sliders Test
WMPopUpButton Dropdown menus in prefs Test
WMTextField (advanced) Multi-line editing Test
Drag & Drop Icon DnD, file DnD Likely needs porting

Phase E: Dialogs & Panels

# Task Key Files Complexity
E1 Window Inspector (per-window attributes) winspector.c High (many widgets)
E2 Info Panel (about dialog) dialog.c Low
E3 Crash Dialog (recovery UI) dialog.c Low
E4 Icon Chooser (browse icon files) dialog.c Medium
E5 Preferences App (full GUI) WPrefs.app/ High

Phase F: wrlib Wayland Integration

# Task Description Complexity Status
F1 RContext for Wayland pixman-backed RContext without X11 Display Medium ✅ Done
F2 RImage → pixman_image conversion Direct path without XImage Low (may exist) ✅ Done
F3 Texture rendering verification Solid, gradient, pixmap textures Verify ✅ Done
F4 Image format loaders PNG/JPEG/GIF/WebP (X11-independent) Verify ✅ Done

Phase F: COMPLETE — all wrlib Wayland paths verified, zero code changes needed.


Phase G: Session & Persistence

# Task Key Files Complexity
G1 Session save/restore (positions, workspaces) session.c Medium
G2 Dock state persistence (icon layout) dock.c state files Low (may work)
G3 Defaults database (~/GNUstep/Defaults/) defaults.c Verify
G4 Autostart (launch apps from session) startup.c Low

Phase H: Advanced Compositor Features

# Task Description Complexity
H1 Multi-monitor (output layout, per-output workspaces) wlr_output_layout, screen.c High
H2 HiDPI / output scaling wlr_output_set_scale, buffer scaling Medium
H3 Gamma control (night light) Already advertised, wire up Low
H4 Screen lock integration ext-session-lock-v1 protocol Medium
H5 Clipboard (copy/paste between clients) wl_data_device (may work via wlroots) Verify
H6 Primary selection Already advertised Verify
H7 Screencopy (screenshots) Already advertised Verify
H8 DMA-BUF (GPU buffer sharing) Already advertised Verify
H9 Input method (IME support) Already advertised Medium

Phase I: EWMH / Foreign Toplevel

# Task Description Complexity
I1 Foreign toplevel list (taskbar integration) Already advertised, verify events Low
I2 XDG activation (focus stealing prevention) Wire to policy Low
I3 Idle inhibit (prevent blank during video) Already advertised Low

Phase J: DRM/KMS Backend (Native)

For running without a host compositor:

# Task Description Complexity
J1 DRM backend wlr_backend_autocreate Medium
J2 libinput (real hardware input) wlroots handles this Low
J3 VT switching wlr_session Low
J4 Multi-GPU Secondary GPU rendering High

Suggested Implementation Order

NOW (daily-driver basics):
  A1. Stacking (raise/lower)
  A2. ~~Maximize~~ ✅
  A3. ~~Shade~~ ✅
  A4. Workspace switching
  ~~A5. Alt-Tab cycling~~ ✅
  A6. Icon/minimize
  A7. Placement (verify)

NEXT (visual completeness):
  B1. App icon rendering (wrlib → pixman)
  F1. wrlib RContext for Wayland
  C1. Slide animations
  C3. Balloon tooltips

THEN (full feature parity):
  D*. Test all WINGs widgets
  E1. Window Inspector
  E4. Icon Chooser
  G*. Session persistence
  B2-B6. Full dock features

LATER (production-ready):
  H1. Multi-monitor
  H2. HiDPI
  J*. DRM/KMS native backend
  E5. WPrefs app

Technical Notes

  1. Rendering pattern: pixman_image_t → wlr_scene_buffer → position in scene graph
  2. wrlib is mostly X11-independent — only context.c and convert.c touch X11
  3. WINGs complex widgets are the hardest remaining work (subtle X11 assumptions in events/coords)
  4. wlroots handles compositor plumbing — protocols advertised, work is wiring to policy
  5. wl_backend.c (316K) should be split into wl_frame.c, wl_menu.c, wl_input.c, wl_toplevel.c, wl_output.c as complexity grows
  6. Many vtable slots are no-ops — stubs exist, need implementation following the pixman→scene pattern

Key Source Files Reference

File Size Purpose
src/backend/backend.h 74K Vtable definition
src/backend/backend_types.h 16K Backend-neutral types
src/backend/wayland/wl_backend.c 316K Wayland compositor
src/backend/x11/x11_backend.c 150K X11 backend
src/window.c 83K Window management
src/dock.c 130K Dock/Clip/Drawers
src/menu.c 61K Menu system
src/actions.c 68K Window actions
src/defaults.c 102K Preferences/config
src/event.c 61K Event dispatch
src/moveres.c 67K Move/resize
src/wmspec.c 61K EWMH compliance
src/main.c 22K Entry point
WINGs/WINGs.h 56K Toolkit public API
wrlib/wraster.h 17K Raster lib public API

Semantic Mapping Appendix

This appendix is the authoritative translation layer between WindowMaker/X11 concepts and their Wayland/wlroots equivalents. Each table maps a WM concept to its X11 mechanism, its Wayland mechanism, the vtable slot(s) that own the boundary, the canonical policy source file(s), and any invariants or trap notes that matter for implementation.

Reading this table: The "Vtable Slot(s)" column names the exact function pointer in WMBackend (backend.h). The "Policy File(s)" column names the .c files in src/ that call those slots. The "Invariants / Traps" column is the most important column — it records semantic mismatches that will silently corrupt state if ignored.


A. Core Window Management (Phase A)

A.1 Window Lifecycle — Manage / Adopt

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Discover new client SubstructureRedirectMask on root; receives MapRequest event xdg_shell::new_surface signal (XDG clients) or xwayland::new_surface signal (XWayland clients) event_pending / event dispatch event.c, window.c XDG clients self-describe via xdg_toplevel; XWayland clients arrive like X11 windows. Two separate code paths in wl_backend.c for wManageWindow.
Read client geometry XGetWindowAttributes xdg_toplevel geometry hint OR wlr_xwayland_surface.width/height window_get_client_attrs → WClientAttributes window.c Wayland clients report desired geometry via configure/ack; the compositor proposes, the client accepts. Never trust initial size as final.
Read client visual/colormap XGetWindowAttributes (depth, Visual*, Colormap) No visual model; depth is always 32 ARGB on Wayland; WClientVisual.visual = NULL client_get_visual_attrs → WClientVisual window.c Frame window creation uses WClientVisual.depth; on Wayland this is always 32. No colormap concept.
Override-redirect detection XWindowAttributes.override_redirect wlr_xwayland_surface.override_redirect (XWayland only); native XDG clients never have override-redirect window_get_client_attrs → WClientAttributes.override_redirect window.c Pure XDG clients should never set override_redirect. If one does, treat as compositor bug.
Reparent client into frame XReparentWindow(client, frame, x, y) Conceptually: compositor positions the wlr_scene_tree containing the client surface as a child of the frame tree client_reparent window.c There is no literal reparent in Wayland. client_reparent in wl_backend.c must re-parent the wlr_scene_tree node. Client does not observe this.
Save-set (crash recovery) XAddToSaveSet / XRemoveFromSaveSet → client maps to root on WM crash No equivalent. Wayland clients die when compositor dies (they hold a wl_display fd). client_add_to_save_set / client_remove_from_save_set (no-ops on Wayland) window.c No-op is correct. Document this as a regression vs X11; Wayland clients cannot survive compositor crash without a separate socket-passing mechanism (e.g. wl_display_roundtrip across exec).
Subscribe to client events XSelectInput(client, event_mask) wl_surface listener registration; input is seat-driven not per-surface subscription client_adopt_attributes window.c On Wayland, you don't "subscribe" to events on individual client windows. Input is delivered via seat. client_adopt_attributes on Wayland only needs to register wlroots signal listeners (commit, destroy, etc.).
Fake group-leader window XCreateSimpleWindow for synthetic WM_HINTS group Allocate a sentinel WNativeWindow ID from a local counter; no actual surface created fake_leader_create / fake_leader_destroy window.c The fake leader is never shown; it is only used as a hash key in the context table. Any stable non-zero WNativeWindow value suffices.

A.2 Window Lifecycle — Unmanage / Destroy

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Client requests close WM_DELETE_WINDOW ClientMessage xdg_toplevel.close event (client-initiated) OR xdg_toplevel_send_close (compositor-initiated) client_close window.c, event.c On Wayland the client may ignore xdg_toplevel_send_close. There is no kill equivalent for native XDG; wl_client_destroy is the nuclear option.
Force-kill client XKillClient wl_client_destroy(wl_resource_get_client(surface->resource)) client_close (forced path) window.c Distinguish "ask nicely" from "kill" in wl_backend.c using a flag or separate vtable slot if needed. Current vtable has one client_close slot.
Client window destroyed DestroyNotify event wlr_surface.destroy signal or wlr_xdg_toplevel.destroy signal event dispatch event.c, window.c Must remove from context table and all stacking lists before freeing WWindow. Race: client may destroy surface between signal and policy handling.
Unmap (hide without destroy) UnmapNotify event wlr_xdg_surface.unmap signal event dispatch event.c, window.c Wayland surfaces emit unmap when the client stops committing a buffer (e.g. minimized). Not the same as iconification; iconification is a compositor-side concept.

A.3 Focus

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Set keyboard focus XSetInputFocus(win, revert_to, time) wlr_seat_keyboard_notify_enter(seat, surface, keycodes, num_keys, &modifiers) focus_set window.c, event.c Wayland focus is seat-scoped, not window-scoped. revert_to has no direct equivalent; the compositor decides where focus reverts on surface destruction.
Focus model: passive Client sets WM_HINTS.input = True; WM calls XSetInputFocus Same behavior: compositor sends wl_keyboard.enter focus_set window.c No change — the compositor always controls focus delivery. WMH_INPUT_HINT is XWayland-only; ignore for native XDG.
Focus model: globally active Client sets WM_HINTS.input = False and handles WM_TAKE_FOCUS ClientMessage XDG has no equivalent; compositor always pushes focus (handled in event.c before vtable call) event.c, window.c Native XDG clients cannot refuse focus from the compositor. Only XWayland clients need the WM_TAKE_FOCUS path. Guard this path with wwin->flags.is_xwayland.
Focus revert on close XSetInputFocus(PointerRoot) or revert to previous Call focus_set on the next window in policy focus stack; Wayland has no PointerRoot concept focus_set window.c Maintain a policy-side focus stack. Never pass PointerRoot to wlr_seat_keyboard_notify_enter.
Keyboard grab (Alt-Tab) XGrabKeyboard(win, ...) wlr_seat_keyboard_start_grab with a custom wlr_keyboard_grab_interface keyboard_grab / keyboard_ungrab cycling.c, event.c Wayland keyboard grabs are compositor-internal; the grab intercepts events before seat delivery. Implement the grab interface in wl_backend.c; cycling.c calls the vtable and never sees wlroots types.

A.4 Stacking

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Raise window XRaiseWindow(win) wlr_scene_node_raise_to_top(&node->node) stacking_raise stacking.c The scene node to raise is the frame's root scene tree node, not the client surface node.
Lower window XLowerWindow(win) wlr_scene_node_lower_to_bottom(&node->node) stacking_lower stacking.c Same: operate on frame tree node.
Restack N windows XRestackWindows(wins, n) Iterate the array and call wlr_scene_node_place_above / place_below in order stacking_restack stacking.c Order of operations matters. Build the target order then apply from bottom up to avoid intermediate states.
WM stacking level (WMLevel) XChangeWindowAttributes + ConfigureWindow with Above/Below sibling hints; EWMH _NET_WM_STATE_ABOVE Maintain per-level scene sub-trees (WL_SCENE_LAYER_*); insert frame node into the correct layer tree on level change stacking_set_level stacking.c, window.c Levels (WMNormalLevel, WMFloatingLevel, WMDockLevel, etc.) must map to distinct wlr_scene_tree nodes. Level ordering is then structural, not a sort operation. This is the key architectural advantage over X11's flat stacking.
Query stacking order XQueryTree(root) returns children in bottom-to-top order Traverse wlr_scene node list stacking_query stacking.c Used by CommitStacking to sync policy stack with server. On Wayland this is a local data structure query, not a round-trip.

A.5 Iconification / Miniwindow

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Iconify (minimize) Set WM_STATE = IconicState; unmap client + frame xdg_toplevel_set_minimized (hint only — client may ignore); unmap frame scene node vtable window_unmap on frame; icon_paint for miniwindow icon.c, actions.c xdg_toplevel_set_minimized is advisory. The compositor must unmap the frame itself and render a miniwindow. Client state is not authoritative here.
Miniwindow (icon tile) Compositor-owned WIcon window rendered with XCreateWindow Compositor-owned wlr_scene_buffer containing pixman-rendered icon tile icon_paint / icon_update_pixmap icon.c, appicon.c The miniwindow is 100% compositor-side. It has no Wayland surface; it is a wlr_scene_buffer placed in the icon layer of the scene graph.
Client snapshot for icon XGetImage on client window wlr_output_layout + wlr_screencopy or render the client's wlr_texture into an RImage client_snapshot icon.c Screencopy requires protocol support (wlr_screencopy_manager_v1). Fallback: render the last committed wlr_texture via pixman blit. Snapshot before unmapping frame.
Deiconify Remap frame + client, set WM_STATE = NormalState Remap frame scene node; xdg_toplevel_unset_minimized (advisory) window_map on frame icon.c, actions.c The client will re-commit its buffers when it receives a configure event after the compositor maps it.

A.6 Workspace Switching

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Workspace concept Virtual desktops via _NET_CURRENT_DESKTOP / _NET_WM_DESKTOP; WM maps/unmaps windows Compositor-side: each workspace is a wlr_scene_tree; switching hides/shows the tree window_map / window_unmap on workspace tree root workspace.c There is no Wayland protocol for workspaces. This is 100% compositor policy. The scene tree approach makes switching O(1) (toggle node visibility) instead of N map/unmap calls.
Move window to workspace Unmap from current virtual desktop, update _NET_WM_DESKTOP, remap on switch Re-parent frame scene node from workspace tree A to workspace tree B stacking_set_level (or a new window_set_workspace vtable slot) workspace.c Scene tree re-parenting is wlr_scene_node_set_parent. Frame position within the new workspace tree must be preserved.
Workspace name badge Rendered as a shaped X window with XShapeCombineMask Rendered as a wlr_scene_buffer overlay (pixman text) placed at compositor layer workspace_badge_render / workspace_badge_update workspace.c The fade animation in badge_update uses RReleaseImage/pixman blending; this is already backend-neutral. Only the "upload to display" step differs.

A.7 Alt-Tab / Switch Panel

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Switch panel overlay Compositor-owned X11 window (WMScreen popup) Compositor-owned wlr_scene_buffer in the overlay layer, rendered via pixman + WINGs switchpanel_enter (vtable hook) switchpanel.c, cycling.c The switch panel must be visible above all client windows. Use a dedicated scene layer above WMDockLevel.
Alt-Tab key grab during cycling XGrabKeyboard on root wlr_seat_keyboard_start_grab with a cycling grab interface that intercepts Tab/Alt-Release keyboard_grab / keyboard_ungrab cycling.c On Wayland there is no concept of "grabbing the keyboard on a window." The grab must be a compositor-internal seat grab that routes events to the cycling state machine before seat delivery.
Window thumbnail in panel XGetImage on each candidate window Render each window's last wlr_texture into an RImage thumbnail (done inside switchpanel.c using client_snapshot) switchpanel.c Thumbnails are expensive on Wayland if using screencopy. Use the last committed texture (cached per-surface) for low latency.

B. Dock & App Icons (Phase B)

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Dock panel surface Compositor X11 window with _NET_WM_WINDOW_TYPE_DOCK Compositor-owned wlr_scene_buffer in the dock scene layer; not a wl_surface frame_paint (dock variant) dock.c The dock is not a client-facing wl_surface. It is a compositor-internal rendered buffer. It does not participate in XDG shell.
App icon surface Pixmap rendered into an X11 subwindow of the dock wlr_scene_buffer child node of the dock scene tree, containing pixman-rendered icon icon_paint / icon_update_pixmap appicon.c, dock.c Icon pixmaps come from wrlib image loaders (X11-independent). The only Wayland-specific step is uploading the pixman buffer to a wlr_scene_buffer.
Dock exclusion zone _NET_WM_STRUT / _NET_WM_STRUT_PARTIAL on dock window wlr_output_set_custom_mode usable area reservation OR policy-side WScreen.usableArea adjustment (policy-side, no vtable slot needed) screen.c, dock.c wlroots has no built-in strut protocol. Update WScreen.usableArea (the left_panel, right_panel etc. margins) directly when dock geometry changes.
Dock drag-and-drop (icon reorder) X11 DnD via _MOTIF_DRAG_AND_DROP or XDND wl_data_device + compositor-side hit-testing; for internal reorder, compositor-managed drag state is sufficient (new vtable slot or handled in dock.c using pointer events) dock.c Internal dock DnD (reorder within dock) does not need wl_data_device at all. Track pointer capture + hit-test against icon slots. External DnD (drop file onto dock icon) needs wl_data_device.
Appicon bounce animation XMapRaised + rapid resize + timer Animate the wlr_scene_buffer scale/position via pixman transforms + compositor timer appicon_bounce appicon.c Use wl_event_loop_add_timer for the animation timer. No X11 server grab needed — compositor renders atomically.
_NET_WM_WINDOW_TYPE hint XGetWindowProperty on _NET_WM_WINDOW_TYPE atom XWayland: same; native XDG: xdg_toplevel has no direct window-type hint; use app_id + heuristics atom_intern + property vtable wmspec.c, window.c For native XDG clients, WNETWindowType must be inferred from app_id or xdg_toplevel role (e.g. xdg_popup → WNW_TYPE_POPUP_MENU). The WNETWindowType enum in backend_types.h is the canonical representation in policy.

C. Visual Polish & Animations (Phase C)

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Iconify/deiconify zoom animation XGrabServer + XDrawRectangle XOR GC on root, stepped timer server_grab is a no-op on Wayland; draw animation frames as pixman-rendered wlr_scene_buffer overlays server_grab / server_ungrab (no-ops), overlay_draw_rect animate.c (or actions.c) The XOR GC approach does not exist in Wayland. Use a full-compositor animation buffer: create a wlr_scene_buffer at the frame's position, animate its geometry each frame via wl_event_loop_add_timer, destroy on completion.
Miniaturize twist/flip (polyline) XDrawLines XOR GC on root Render polyline into a pixman overlay buffer per frame overlay_draw_lines animate.c Same pattern as zoom above. The XOR compositing model is gone; each frame must be a fresh render.
Workspace badge fade Pre-computed RImage blend steps, updated via XSetWindowBackgroundPixmap Update wlr_scene_buffer with each blend step image workspace_badge_update workspace.c Already backend-neutral up to the "upload" step. The vtable slot exists; wl_backend.c must call wlr_scene_buffer_set_buffer with the new pixman image each step.
Cursor themes XcursorLibraryLoadImages + XDefineCursor wlr_xcursor_manager_load(theme, size) + wlr_seat_pointer_notify_frame (compositor-internal; no vtable slot) event.c or wl_backend.c Cursor theme loading is entirely compositor-side. The WCUR_* constants (defined in backend.h) map to wlr_xcursor_manager names. Mapping table lives in wl_backend.c.
Window opacity _NET_WM_WINDOW_OPACITY property (32-bit alpha) wlr_scene_node_set_alpha on frame scene tree root netwm_set_opacity wmspec.c wlr_scene_node_set_alpha accepts a float [0.0, 1.0]. Divide the _NET_WM_WINDOW_OPACITY 32-bit value by 0xFFFFFFFF.
Balloon tooltip Shaped X11 window (override-redirect) wlr_scene_buffer overlay at pointer position, pixman-rendered text balloon_show / balloon_hide appicon.c, dock.c Position tooltip using compositor pointer coordinates (from last motion event), not XQueryPointer.

D. WINGs Widgets (Phase D)

WINGs itself has split X11/Wayland backends. The key semantic mismatches are in coordinate systems and event delivery.

Concept X11 Mechanism Wayland Mechanism Affected Files Invariants / Traps
Widget window XCreateWindow (override-redirect) wlr_scene_buffer (compositor-owned surface, not a wl_surface) WINGs/WView_wl.c WINGs Wayland views are compositor-internal rendering targets, not client surfaces. They receive input via compositor hit-testing, not X11 event delivery.
Widget input (keyboard) XSelectInput + FocusIn/FocusOut events Compositor routes seat keyboard events to focused WINGs view via the ring-buffer event system WINGs/WEvent_wl.c WINGs keyboard focus is compositor-managed. WMSetFocusToWidget must call the compositor focus mechanism, not XSetInputFocus.
Widget input (pointer) ButtonPress/Release/Motion events from X server Compositor hit-tests against scene graph; delivers synthetic WMEvent to WINGs event ring buffer WINGs/WEvent_wl.c Pointer coordinates in WINGs events must be surface-local (relative to the WINGs view's scene node position), not root-relative.
Widget coordinate system Root-relative via XTranslateCoordinates Scene-graph-relative: each node's position is relative to its parent tree coords_translate Coordinate translation on Wayland is local arithmetic: root_pos = node_pos + parent_pos + ... up to root. No round-trip.
WMColorPanel Uses X11 colorspace APIs for color matching Use platform-independent color math in WINGs; no X11 color allocation WINGs/WColorPanel.c or _wl.c X11 color cells and colormaps do not exist on Wayland. WMColor on Wayland is always 32-bit ARGB; WMCreateRGBColor maps directly to pixman color.
WMFontPanel / font rendering X11 core fonts + Xft PangoFT2 (already implemented in WINGs Wayland backend) WINGs/WFont_wl.c PangoFT2 is already in place. Verify that font metrics (ascent, descent, string width) returned by WINGs Wayland match the X11 path to avoid layout differences in dialog panels.
Drag and Drop (WINGs DnD) XDND protocol (XdndEnter, XdndDrop, etc.) via X11 ClientMessage events wl_data_device (for client↔client DnD) or compositor-internal DnD for WINGs↔WINGs WINGs/WDND_wl.c (needs creation) XDND is X11-only. Native Wayland DnD requires wl_data_source, wl_data_offer. For internal WINGs DnD (e.g. dragging a color swatch), a compositor-internal mechanism suffices.
WMScrollView / WMScroller XCreateWindow child windows + expose events for scroll indicators Pixman-rendered scroll indicators as child wlr_scene_buffer nodes WINGs/WScroller_wl.c (verify) Scrolling involves clipping content. On Wayland, clip via wlr_scene_rect masks or by scissoring the pixman render target.
Modal loop (dialog) XCheckMaskEvent loop with XSync Compositor tick loop: wl_display_flush_clients + wl_event_loop_dispatch(0) per iteration WINGs/WApplication_wl.c The modal loop must tick the compositor so pending Wayland protocol messages are processed while the dialog is open. The Run Dialog already does this correctly; reuse that pattern.

E. Dialogs & Panels (Phase E)

Concept X11 Mechanism Wayland Mechanism Key Files Invariants / Traps
Dialog window Transient-for parent (WM_TRANSIENT_FOR) xdg_popup or xdg_toplevel with set_parent; for compositor-internal dialogs, a wlr_scene_buffer overlay winspector.c, dialog.c Compositor-internal dialogs (WINGs-based) are scene graph overlays, not XDG surfaces. They do not participate in wl_data_device or XDG decoration.
Window Inspector attribute display Read/write X11 properties live Read/write WWindow struct fields + call vtable for any display-side changes (e.g. focus_set, stacking_raise) winspector.c All WWindow fields are already backend-neutral (populated by vtable calls during wManageWindow). The inspector can read them directly without vtable calls.
Preferences App Fork + exec WPrefs.app as a separate X11 process Fork + exec WPrefs.app; it connects as a Wayland client (XDG toplevel) WPrefs.app/main.c WPrefs must be ported to use WINGs Wayland backend. It will run as a regular Wayland client, not a compositor-internal window. Communication with the WM core via the defaults database (~/GNUstep/Defaults/) is already backend-neutral.

F. wrlib Wayland Integration (Phase F)

Concept X11 Mechanism Wayland Mechanism Key Files Invariants / Traps
RContext creation RCreateContext(dpy, screen, visual, colormap, ...) → XCreateGC, server-side drawing RCreateContextForWayland() (to be added) → pixman_image_t as backing; no Display* wrlib/context.c The RContext type has X11 fields (dpy, drawable, gc). For Wayland, these must be NULL/0 with a parallel pixman_image_t *pixman_surface field. Functions that use dpy must be guarded by checking rcontext->attribs.flags for a Wayland-context flag.
RImage → display RConvertImage → XPutImage / XCreatePixmap RConvertImageToPixman → pixman_image_t; then wlr_scene_buffer_set_buffer via vtable wrlib/convert.c convert.c is the only wrlib file that touches X11 Pixmap/XImage APIs. A RConvertImageToPixman path already exists or needs to be added. Policy code calls the vtable upload slot, never wrlib directly.
Image format loaders PNG/JPEG/GIF/TIFF via respective libraries; X11-independent Same libraries; same code wrlib/png.c, jpeg.c, gif.c, tiff.c Loaders are already X11-independent. Verify that RLoadImage does not call any X11 API for format detection or error display.
Texture rendering RenderImage → XCopyArea via GC RenderImageToPixman → pixman composite operation wrlib/render.c render.c uses XCopyArea for the final blit. Add a pixman blit path alongside it.
Gradient/solid textures RRenderGradient, RRenderSolid → draws into server pixmap Same operations into pixman buffer wrlib/gradient.c, texture.c These operate on RImage (CPU-side pixel buffer); they are already X11-independent. Only the final "upload to display" step differs.

G. Session & Persistence (Phase G)

Concept X11 Mechanism Wayland Mechanism Key Files Invariants / Traps
Session save (window positions) Read root-relative geometry via XGetWindowAttributes Read from WWindow.frame_x/y (already updated by vtable geometry calls) session.c Window positions are already tracked in WWindow by policy code. No vtable call needed to read them at save time.
Session restore Re-launch apps, match by WM_CLASS / WM_COMMAND, position via WNH_P_POSITION hint Same: re-launch apps; XDG clients use app_id as the match key instead of WM_CLASS session.c WM_CLASS ↔ app_id mapping is not 1:1. Session restore must handle both X11 (XWayland) and native XDG clients. Consider storing app_id in session file alongside WM_CLASS.
Defaults database Read/write ~/GNUstep/Defaults/*.plist via WINGs WMUserDefaults Same path; WMUserDefaults is X11-independent defaults.c, WINGs/userdefaults.c Already backend-neutral. No action needed.

H. Advanced Compositor Features (Phase H)

H.1 Multi-Monitor

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Head/output discovery Xinerama XineramaQueryScreens → WXineramaInfo wlr_output_layout + new_output signal; WHeadGeometry array (defined in backend_types.h) screen_open populates WScreen.heads[] screen.c WHeadGeometry (in backend_types.h) is already defined as the backend-neutral replacement for WMRect-based Xinerama data. Populate it from wlr_output_layout_get_box in screen_open.
Per-head usable area WScreen.usableArea computed from Xinerama + struts Same WScreen.usableArea fields; updated from wlr_output_layout geometry minus dock margins (policy-side) screen.c, dock.c The policy computation is the same. Only the input geometry source changes (wlroots output layout vs Xinerama).
Output hotplug RandR OutputChangeNotify event wlr_output.destroy signal (disconnect) + new_output signal (connect) event dispatch screen.c Handle output removal by migrating windows to the remaining output(s) before destroying the WScreen for that head.
Cursor across outputs X11 cursor is global wlr_seat_pointer_notify_frame handles cursor across outputs automatically via the output layout (compositor-internal) wl_backend.c No policy action needed. wlroots manages cursor output transitions.

H.2 HiDPI / Scaling

Concept X11 Mechanism Wayland Mechanism Vtable Slot(s) Policy File(s) Invariants / Traps
Output scale Xft.dpi resource; manual pixel doubling wlr_output_set_scale(output, scale) (compositor-internal) wl_backend.c Set scale per-output. Frame buffers must be rendered at scale × logical pixels. All pixman render operations must account for scale factor.
Buffer vs logical coordinates X11: one coordinate space Wayland: logical coordinates (what policy sees) vs buffer coordinates (what compositor renders) All vtable geometry slots window.c, moveres.c Policy code works in logical coordinates throughout. The backend multiplies by wlr_output.scale when allocating pixman buffers and wlr_scene_buffer sizes. WScreen.width/height are logical.
Font scaling Xft.dpi PangoFT2 DPI setting per output WINGs/WFont_wl.c PangoFT2 allows per-context DPI. Set it from wlr_output.scale * 96.0 when creating font contexts for each output's UI.

H.3-H9 Protocol Features

Feature X11 Mechanism Wayland Mechanism Key Notes
Clipboard CLIPBOARD selection (XSetSelectionOwner, SelectionRequest/Notify) wl_data_device_manager + wl_data_source / wl_data_offer; wlroots implements the device manager Already advertised by wlroots. Wire wl_data_device signals to the selection policy in selection.c. XWayland bridge auto-syncs CLIPBOARD ↔ Wayland clipboard.
Primary selection PRIMARY selection zwp_primary_selection_device_manager_v1 Already advertised. Same pattern as clipboard.
Screen lock No standard X11 mechanism (DPMS + XScreenSaver) ext-session-lock-v1 (lock surface covers all outputs) Lock surface must be in the highest compositor layer. wlroots has wlr_session_lock_manager_v1.
Screencopy XGetImage wlr_screencopy_manager_v1 Already advertised. Used for client snapshots and compositor-side screenshots.
DMA-BUF DRI2/DRI3 extension zwp_linux_dmabuf_v1 Already advertised. Client GPU buffers are directly scannable without CPU copy.
IME XIM protocol zwp_input_method_v2 + zwp_text_input_v3 wlroots has support. Wire to WINGs text field input pipeline.
Idle inhibit XScreenSaverSuspend zwp_idle_inhibit_manager_v1 Already advertised. Clients request inhibit; compositor honors it by suppressing idle timer.
Gamma control XF86VidMode / RandR CRTC gamma wlr_gamma_control_manager_v1 Already advertised. Wire UI control (night light toggle) to wlr_output_set_gamma.

I. EWMH / Foreign Toplevel (Phase I)

Concept X11 Mechanism Wayland Mechanism Key Files Invariants / Traps
Taskbar window list _NET_CLIENT_LIST / _NET_CLIENT_LIST_STACKING root properties ext-foreign-toplevel-list-v1 signals (new_toplevel, closed, state updates) wmspec.c Already advertised. On window manage/unmanage in wManageWindow/wUnmanageWindow, emit foreign_toplevel created/destroyed events. Update title/app_id on property change.
_NET_WM_STATE (maximized, fullscreen, etc.) _NET_WM_STATE property + ClientMessage xdg_toplevel.set_maximized / set_fullscreen requests; wlr_xdg_toplevel_set_maximized from compositor side wmspec.c, actions.c XDG toplevel state is negotiated (compositor sets, client acks). _NET_WM_STATE is authoritative on X11 (WM sets it). On Wayland, track state in WWindow and only call the xdg toplevel API; do not write _NET_WM_STATE for native XDG clients.
XDG activation (focus stealing prevention) _NET_WM_USER_TIME + policy checks xdg_activation_v1: client requests activation token; compositor decides whether to honor it wmspec.c Wire xdg_activation_token requests to the policy focus-stealing check in window.c. Allow or deny based on WWindow.user_time equivalent.
Idle inhibit from clients No standard zwp_idle_inhibit_manager_v1 client request wmspec.c or wl_backend.c Track active inhibitors per-client. Suppress compositor idle timer while any inhibitor is active.

Vtable Slot Quick-Reference

The following table maps every vtable slot group to its implementation status and primary Wayland primitive. Use this when writing new wl_backend.c functions.

Vtable Group Slots Wayland Primitive Status
Display lifecycle display_open/close/post_open/screen_count/default_screen wl_display, wl_event_loop ✅ Implemented
Atom intern atom_intern No-op (returns 0) ✅ No-op
Event loop event_loop_run/terminate wl_display_run / wl_display_terminate ✅ Implemented
Screen lifecycle screen_open/init_display/close wlr_backend, wlr_renderer, wlr_output_layout ✅ Implemented
Frame window frame_create_toplevel/child/destroy/configure/paint wlr_scene_tree, wlr_scene_buffer, pixman ✅ Implemented
Context table context_save/find/delete Hash table (WNativeWindow → object) ✅ Implemented
Focus / key grabs focus_set, key_grab/ungrab_all, button_grab/ungrab_all wlr_seat_keyboard_notify_enter, compositor-internal grab ✅ Implemented
Pointer grabs pointer_grab/ungrab/warp/query, pointer_query_full wlr_seat_pointer_start_grab, wlr_cursor ✅ Implemented
Stacking stacking_restack/raise/lower/query/set_level wlr_scene_node_raise/lower_to_top/bottom, scene layer trees ✅ Implemented
Event queue event_flush/sync/pending, event_drain_motion, event_discard_enter_leave_until wl_display_flush_clients, wl_event_loop_dispatch ✅ Implemented
Properties prop_get_normal_hints/wm_hints/wm_class/window_fetch_name/window_get_icon_name/prop_get_wmaker_menu wlr_xwayland_surface fields (XWayland) or no-op (native XDG) ✅ Implemented (XWayland path)
Window visibility window_map/map_raised/unmap/move/resize wlr_scene_node_set_enabled, wlr_scene_node_set_position ✅ Implemented
Workspace badge workspace_badge_render/update pixman text render → wlr_scene_buffer_set_buffer ✅ Implemented
Server grab server_grab/ungrab No-op (Wayland is asynchronous; no server grab concept) ✅ No-op
Overlay drawing overlay_draw_lines/rect/line/segments/text_width/draw_string pixman overlay buffer per animation frame ⚠️ Stub — needs pixman animation buffer
Window attributes window_get_rect/get_client_attrs/client_get_visual_attrs wlr_scene_node position / wlr_xwayland_surface attrs ✅ Implemented
Client snapshot client_snapshot wlr_screencopy or last-committed wlr_texture blit ⚠️ Partial
Desktop refresh refresh_desktop No-op (compositor repaints on damage; no manual refresh needed) ✅ No-op
Event subscription window_select_events No-op (Wayland events are signal-based, not mask-based) ✅ No-op
Window border window_set_border/set_border_width wlr_scene_rect border nodes in frame tree ⚠️ Verify
Pointer warp absolute pointer_warp_to wlr_cursor_warp ✅ Implemented
Expose drain event_flush_expose No-op (no Expose concept in Wayland) ✅ No-op
Keyboard grab keyboard_grab/ungrab, keysym_to_keycode wlr_seat_keyboard_start_grab, xkb_keymap_key_by_name ✅ Implemented
Grab change pointer_change_grab wlr_seat_pointer_start_grab (new grab with updated params) ⚠️ Verify
Coord translation coords_translate Local arithmetic on scene node positions ✅ Implemented
Window save-under window_set_save_under No-op (Wayland compositors handle occlusion automatically) ✅ No-op
Background window_set_background/set_background_pixmap wlr_scene_rect or wlr_scene_buffer fill node ⚠️ Verify
Keysym lookup keysym_from_keycode xkb_state_key_get_one_sym ✅ Implemented
Window clear/submap window_clear/map_subwindows wlr_scene_buffer damage mark / child node enable ⚠️ Verify
Pixmap free pixmap_free No-op (pixman images are CPU-side; freed via pixman_image_unref) ✅ No-op
Client reparent client_reparent wlr_scene_node_reparent ✅ Implemented
Save-set client_add/remove_from_save_set No-op ✅ No-op
Client close client_close xdg_toplevel_send_close / wl_client_destroy ✅ Implemented
Fake leader fake_leader_create/destroy Allocate/free a synthetic WNativeWindow ID ✅ Implemented
Synth configure client_synth_configure wlr_xdg_surface_schedule_configure (XDG) or no-op (XWayland handles it) ⚠️ Verify

Status legend: ✅ Implemented and tested · ⚠️ Stub, partial, or needs verification · ❌ Not started


Semantic Mismatch Summary

These are the highest-risk X11↔Wayland semantic gaps — places where naive translation produces silent bugs.

# Mismatch X11 Behavior Wayland Behavior Risk
M1 Focus model for "globally active" clients WM sends WM_TAKE_FOCUS ClientMessage; client may accept or ignore Compositor always pushes focus; client cannot refuse Medium — WM_TAKE_FOCUS path must be XWayland-only
M2 Window geometry negotiation WM sets size/position; client observes via ConfigureNotify Compositor proposes via xdg_toplevel_configure; client acks or requests different High — never assume XDG client accepted proposed geometry until ack received
M3 Override-redirect semantics Client's override_redirect=True means WM must not decorate or reposition XWayland preserves this; native XDG clients have no equivalent Medium — guard O-R handling with is_xwayland flag
M4 Save-set / compositor crash X11 clients survive WM crash via save-set Wayland clients die when compositor fd closes Low (document as regression; consider --no-reparent mode)
M5 Stacking order query latency XQueryTree is a server round-trip; may be stale wlr_scene traversal is local and always current Low (actually an improvement, but code assuming round-trip latency may misbehave)
M6 XOR drawing / server grab XGrabServer + XOR GC enables atomic multi-window animation No server grab; no XOR; each frame is a fresh compositor render High — all animation code using XOR GC must be rewritten as per-frame pixman renders
M7 _NET_WM_STATE write semantics WM writes the property; it is authoritative XDG state is negotiated; WM proposes, client acks High — do not write _NET_WM_STATE for native XDG clients
M8 Workspace / virtual desktop No standard; WM maps/unmaps windows No standard; compositor shows/hides scene trees Low (both are custom; scene tree approach is cleaner)
M9 Drag and drop (XDND) X11 ClientMessage protocol; WINGs DnD uses this wl_data_device for client DnD; compositor-internal for WINGs DnD Medium — two separate DnD code paths needed
M10 Pointer coordinates in WINGs events Root-relative (from X11 event fields) Surface-local (relative to WINGs view scene node) High — coordinate mismatch causes mis-clicks in all WINGs widgets