windowmaker-wl/WAYLAND_ROADMAP.md

605 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WindowMaker Wayland Port — Roadmap
Last updated: 2026-05-23
## 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-23)
- 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)
- **A4 Workspace switching**: map/unmap via vtable already worked; added workspace name badge overlay (pixman text rendering in WL_LAYER_OVERLAY, fade animation via new `workspace_badge_get_background` vtable slot)
- **A7 Placement**: verified pure policy code, zero backend calls needed
- **B1 App icon rendering**: wl_appicon_paint/wl_icon_paint already implemented; fixed wl_appicon_show (was no-op, now calls paint_app_icon for initial placement + map)
- **Phase B complete** (2026-05-23):
- B2: dock_shadow frame_buf for icon drag ghost overlay (commit 3d10085)
- B3: Auto-launch — pure policy (fork/exec), works as-is
- B4: Clip workspace indicator — workspace number + name rendered on clip icon (commit 3687ff0)
- B5: Drawer icon renders directional arrow (commit 9ed1c19)
- B6: Icon bounce — already works (`doAppBounce` uses vtable `window_move`; vtable slot correctly a no-op, same as X11)
- **Bug fixes** (2026-05-23):
- Titlebar buttons not responding to clicks (commit 8fb4083)
- Modal dialogs not receiving mouse clicks (commit 68d4301)
- WINGs widget clicks on Wayland — child view hit-testing (commit ee8489e)
- WINGs dialog click-to-focus lookup (commits 7fe8121, 403ee93)
- Titlebar button scene_bufs enabled for click and visibility (commit 0cf867d)
- Button scene_bufs raised above titlebar (commit a34d828)
- Titlebar button rendering — proper colors, bevel, pressed state (commit 5a4d989)
- Button bevel colors derived from titlebar background (commit f3cb593)
- **Phase C complete** (2026-05-23):
- C1: Slide animations — already works (misc.c calls vtable window_move per frame)
- C2: Menu scrolling — pure policy code, already works
- C3: Balloon tooltips — pixman text render → wlr_scene_buffer overlay in WL_LAYER_OVERLAY
- C4: Cursor themes — wlr_xcursor_manager + wl_pointer_change_cursor already in place
- C5: Window opacity — reads _NET_WM_WINDOW_OPACITY from XWayland, applies wlr_scene_buffer_set_opacity
- **Phase D complete** (2026-05-23): WINGs widget X11 elimination
- All shared widget code has zero X11 executing on Wayland path
- 7 commits: wwindow.c vtable (264679a), wballoon/wpopupbutton/wtext (9eecb25), wsplitview/wtextfield xor_fill_rect (2dce454), selection vtable (9ccfd33), wcolorpanel render backend (0e26aa8), magnifier vtable callback (8ec3380), dead code removal (7077e00)
- New vtable slots: xor_fill_rect, selection_request, magnify_callback, screen_grab_image (removed)
- Verified safe (X11-only paths): winputmethod.c, wappresource.c, DnD, wballoon drawing, wtextfield convertSelection
- Bug fix: NULL toplevel crash on Wayland expose dispatch (1ab108f)
- **src/ core audit complete** (2026-05-23):
- All X11 in src/ already routed through wm_backend-> dispatch
- PropGetCheckProperty → wm_backend->prop_get_check_property
- xdnd.c → wm_backend->xdnd_* (no-ops on Wayland)
- wmspec.c → all property access through wm_backend
- event.c selection handler → only reached with raw X11 events
- **Result: entire codebase (WINGs + src/) has zero X11 executing on Wayland path**
---
## 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 | ✅ Done |
| 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 (pure policy, no backend calls) | ✅ Done |
---
## 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 | ✅ Done |
| B2 | ~~Dock icon drag & drop (reorder, add/remove)~~ | dock.c DnD handlers | ✅ Done |
| B3 | ~~Dock auto-launch on login~~ | dock.c session restore | ✅ Done |
| B4 | ~~Clip workspace indicator (number, arrows)~~ | dock.c clip paint | ✅ Done |
| B5 | ~~Drawer support (horizontal dock extension)~~ | dock.c drawer subsystem | ✅ Done |
| B6 | ~~Icon bounce on launch~~ | vtable appicon_bounce | ✅ Done |
**Phase B: COMPLETE**
---
## Phase C: Visual Polish & Animations
| # | Task | Key Files | Complexity |
|---|------|-----------|------------|
| C1 | ~~Slide animations (iconify/deiconify zoom)~~ | misc.c → vtable window_move | ✅ Done (already works) |
| C2 | ~~Menu scrolling (long menus)~~ | menu.c scroll logic | ✅ Done (pure policy) |
| C3 | ~~Balloon tooltips (dock icon hover)~~ | vtable balloon_show | ✅ Done |
| C4 | ~~Cursor themes (xcursor loading)~~ | wlr_xcursor_manager | ✅ Done (already works) |
| C5 | ~~Window opacity (NETWM alpha)~~ | vtable netwm_update_opacity | ✅ Done |
**Phase C: COMPLETE**
---
## 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~~ ✅
~~C2. Menu scrolling~~ ✅
~~C3. Balloon tooltips~~ ✅
~~C4. Cursor themes~~ ✅
~~C5. Window opacity~~ ✅
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 |