605 lines
54 KiB
Markdown
605 lines
54 KiB
Markdown
# 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 |
|