From e1eaf1792506bff16a66e5b3b22990590c1a868a Mon Sep 17 00:00:00 2001 From: lkn Date: Tue, 26 May 2026 15:55:25 +0200 Subject: [PATCH] docs: replace planning docs with forward-looking ROADMAP.md Remove completed planning documents (WAYLAND_ROADMAP.md, ACTION_PLAN.md, ACTION_PLAN_PROMPTS.md). Update ARCHITECTURE.md and BACKEND_ABSTRACTION.md with current file sizes and completion status. Add ROADMAP.md covering remaining work prioritized for daily-driver use. --- ACTION_PLAN.md | 625 -------------------------------------- ACTION_PLAN_PROMPTS.md | 666 ----------------------------------------- ARCHITECTURE.md | 39 +-- BACKEND_ABSTRACTION.md | 17 +- ROADMAP.md | 170 +++++++++++ WAYLAND_ROADMAP.md | 604 ------------------------------------- 6 files changed, 202 insertions(+), 1919 deletions(-) delete mode 100644 ACTION_PLAN.md delete mode 100644 ACTION_PLAN_PROMPTS.md create mode 100644 ROADMAP.md delete mode 100644 WAYLAND_ROADMAP.md diff --git a/ACTION_PLAN.md b/ACTION_PLAN.md deleted file mode 100644 index b203782..0000000 --- a/ACTION_PLAN.md +++ /dev/null @@ -1,625 +0,0 @@ -# Wayland Port — Concrete Action Plan - -Correlated with WAYLAND_ROADMAP.md phases and the abstraction leak analysis. - ---- - -## How to Read This Plan - -Each task is **mechanical**: it names the exact file(s), the exact Xlib calls or X11 types to eliminate, and the exact replacement mechanism. Tasks are grouped by roadmap phase and ordered by dependency. - -**Task format:** -- **ID**: `{Phase}.{Number}` -- **File(s)**: exact source files -- **Operation**: what to do -- **Xlib calls eliminated**: count -- **Blocked by**: prerequisite tasks - ---- - -## Wave 1 Complete (2026-05-18) - -All 8 signature change tasks completed (changes since commit 9943c05): - -| Task | Description | Status | -|------|-------------|--------| -| A5.1 | `wSwitchPanelHandleEvent`: `XEvent*` → `WMEvent*` | ✅ | -| A5.2 | Remove XEvent synthesis in `cycling.c` | ✅ | -| B.3 | `wDockReceiveDNDDrop`: `XEvent*` → `WMEvent*` | ✅ | -| E.3 | `wRootMenuPerformShortcut`: `XEvent*` → `WMEvent*` | ✅ | -| E.4 | `wClientConfigure`/`wClientCheckProperty`: X11 types → `WMEvent*` | ✅ | -| R.1 | `framewin.c`: `_x11_raw` → `WMEvent` | ✅ | -| R.2 | `icon.c`: `_x11_raw` → `WMEvent` | ✅ | -| R.3 | `menu.c`: `_x11_raw` → `WMEvent` | ✅ | - -**Results:** -- `_x11_raw` sites in `src/`: 16 → 9 (7 eliminated in policy code) -- Build verified clean (no new warnings, no regressions) -- Crash regression (use-after-free on modal dialog close) fixed: - - `W_IsValidView()` validates stale `_view_target` against `view_table` at fetch boundary in `wevent_wl.c`, plus defense-in-depth in `wview.c` and `wevent.c` - - Scene graph cleanup: `wl_destroy()` calls `W_ViewDestroyBackingFn` → `wl_view_destroy_backing_impl()` to destroy `wlr_scene_node` and `wlr_buffer_drop` when WINGs toplevels close - ---- - -## Wave 2 Complete (2026-05-18) - -All 4 mechanical elimination tasks completed (changes since commit 0950b22): - -| Task | Description | Status | -|------|-------------|--------| -| A4.1 | `workspace.c`: remove 5 `USE_XINERAMA` guards | ✅ | -| B.1 | `appicon.c`: `XClearArea` → `wm_backend->window_clear` | ✅ | -| R.5 | `usermenu.c`: `XStringToKeysym` → `xkb_keysym_from_name`, `XSendEvent` → `dpy` guard | ✅ | -| R.7 | `window.c`: remove 4 `USE_XSHAPE` guards | ✅ | - -**Results:** -- `_x11_raw` sites in `src/`: 9 → 9 (no new _x11_raw; these tasks eliminated X11 calls, not _x11_raw) -- Build verified clean (no new warnings, no regressions) -- C.1 skipped: XOR rubber-band drawing in `moveres.c` is inside `#if 0` — dead code, no action needed - ---- - -## Bug Fixes - -### Native XDG client rendering fix (commit 8d963e7) - -**Problem:** Native XDG clients (e.g. foot terminal, gedit) would render once on initial map but never re-render on buffer commits. The compositor received `wl_surface.commit` events but did not schedule output frame refreshes. - -**Root cause:** `surface_commit_listener` in `wl_backend.c` was registered but did not call `wlr_output_schedule_frame()`. Without this call, the compositor never redrew the output after the initial frame. - -**Fix:** The `surface_commit_listener` callback now calls `wlr_output_schedule_frame()` on every buffer commit, ensuring the compositor redraws the output and picks up the new buffer content. - ---- - -## Phase F: wrlib Wayland Integration - -*Do this first — other phases depend on wrlib working without X11.* - -### DONE ~~F.1 Remove gratuitous `#include ` from pure files~~ -- **Files**: `wrlib/color.c`, `wrlib/convolve.c`, `wrlib/flip.c`, `wrlib/load.c`, `wrlib/load_ppm.c`, `wrlib/misc.c`, `wrlib/raster.c`, `wrlib/rotate.c`, `wrlib/save.c`, `wrlib/scale.c` -- **Operation**: Each file includes `X11/Xlib.h` but makes zero Xlib calls. They get it transitively via `#include "wraster.h"`. The include is in `wraster.h.in` itself (line 47), so these files can't avoid it without changing the public header. Two options: - - (a) Guard the X11 includes in `wraster.h.in` with `#ifndef WRLIB_NO_X11` and define that in Wayland builds, OR - - (b) Accept the transitive include (it's a type definition header, not a link dependency) and move on. -- **Recommendation**: Option (b) — this is cosmetic. Skip and move to substantive tasks. -- **Xlib calls eliminated**: 0 (cosmetic only) - -### F.2 ~~Verify `RCreateContextWayland()` completeness~~ ✅ -**Status**: DONE — all fields verified correct, no issues. -- **File**: `wrlib/context.c:725-775` -- **Operation**: Read `RCreateContextWayland()`. Verify it sets all fields that `RConvertImage` and image loaders need: `depth`, `vclass`, `red_offset`, `green_offset`, `blue_offset`, `black`, `white`, `convert_image`, `convert_image_mask`. Verify `dpy`, `cmap`, `copy_gc`, `visual`, `drawable` are NULL/0. -- **Test**: Call `RCreateContextWayland(1920, 1080, NULL)`, then `RLoadImage(ctx, "test.png", 0)`, then `RConvertImage(ctx, img, &pixmap)`. Confirm the pixmap is a `pixman_image_t*` cast to `Pixmap`. -- **Blocked by**: nothing - -### F.3 ~~Verify `wl_convert_image` / `wl_convert_image_mask`~~ ✅ -**Status**: DONE — RGBA→ARGB32 conversion correct, alpha handling correct, no bugs. -- **File**: `wrlib/convert_wl.c` (58 lines) -- **Operation**: Read the implementation. Verify RGBA→ARGB32 byte order conversion is correct. Verify alpha channel handling for `RRGBFormat` (no alpha) vs `RRGBAFormat`. Verify the returned `Pixmap` is `(Pixmap)(uintptr_t)pixman_image_t*`. -- **Test**: Load a PNG with alpha, convert, verify pixel values in the pixman image. -- **Blocked by**: F.2 - -### F.4 ~~Verify image format loaders are X11-independent~~ ✅ -**Status**: DONE — all 7 loaders verified X11-independent, zero dpy/cmap/visual references. -- **Files**: `wrlib/load_png.c`, `wrlib/load_jpeg.c`, `wrlib/load_gif.c`, `wrlib/load_tiff.c`, `wrlib/load_webp.c`, `wrlib/load_ppm.c`, `wrlib/load_xpm_normalized.c` -- **Operation**: Confirm each loader takes `RContext*` only for error reporting (or not at all) and returns `RImage*` without touching `ctx->dpy`. Grep each file for `->dpy`, `->cmap`, `->visual`. -- **Expected result**: All clean except `load_xpm.c` (uses `XParseColor`). -- **Blocked by**: nothing - -### F.5 ~~Provide Wayland-safe XPM loading path~~ ✅ -**Status**: DONE — already wired up at load.c:199-204 — ctx->dpy==NULL check dispatches to RLoadXPMNormalized. -- **File**: `wrlib/load_xpm.c` (180 lines) — uses `XParseColor` for color name→RGB -- **Operation**: `load_xpm_normalized.c` (459 lines) already exists as a pure-C XPM parser that doesn't need X11. Verify that `RLoadImage()` in `wrlib/load.c` dispatches to `load_xpm_normalized.c` when `ctx->dpy == NULL`. If not, add a check: - ```c - if (ctx->dpy == NULL) - return RLoadXPMNormalized(ctx, file); - else - return RLoadXPM(ctx, file); - ``` -- **Xlib calls eliminated**: 2 (in the Wayland path) -- **Blocked by**: F.2 - ---- - -### Phase F Summary - -All five Phase F tasks verified — the wrlib Wayland integration path is **complete with zero code changes required**: - -| Task | Result | -|------|--------| -| F.1 | Gratuitous `#include ` in 10 pure files — cosmetic only, accepted per option (b). | -| F.2 | `RCreateContextWayland()` — all fields verified correct. depth=32, TrueColor, RGBA offsets, black/white, convert function pointers all set. dpy/cmap/visual/drawable NULL'd. | -| F.3 | `wl_convert_image` / `wl_convert_image_mask` — RGBA→ARGB32 byte order correct, alpha handling correct for both RRGBFormat and RRGBAFormat, mask thresholding with stride-aware indexing, Pixmap cast idiomatic. No bugs. | -| F.4 | All 7 image format loaders verified X11-independent — zero references to `->dpy`, `->cmap`, `->visual`. | -| F.5 | Wayland-safe XPM loading already wired at `load.c:199-204` — `dpy == NULL` check dispatches to `RLoadXPMNormalized` (pure-C parser) vs `RLoadXPM` (X11/XParseColor). | - -Key architecture point: wrlib's `convert_image` function pointer (`context->convert_image`) is the single abstraction seam — set to `wl_convert_image` by `RCreateContextWayland()` and `x11_convert_image` by `RCreateContext()`. Everything else is either pure image manipulation (portable) or hardcoded X11 (`xutil.c`, `xpixmap.c`, `convert_x11.c`) that is dead code on the Wayland path. - ---- - -## Phase A: Core Window Management Gaps - -### A4: Workspace Switching - -#### A4.1 ~~Eliminate `USE_XINERAMA` guards in `workspace.c`~~ ✅ -- **File**: `src/workspace.c` — 5 `#ifdef USE_XINERAMA` sites (lines 252, 280, 295, 303, 328) -- **Operation**: Each guard selects between per-monitor and whole-screen geometry for workspace name display. The monitor geometry should come from `WScreen` fields (already populated by the backend), not from Xinerama calls. For each site: - 1. Read the `#ifdef USE_XINERAMA` block and the `#else` block - 2. Replace both with code that reads `wGetHeadForPointerLocation(scr)` or equivalent backend-neutral monitor query (these functions already exist and are backend-abstracted) - 3. Delete the `#ifdef` -- **Xlib calls eliminated**: 0 (these are compile-time guards, not direct Xlib calls; the Xinerama calls are in the backend) -- **Blocked by**: nothing - -### A5: Alt-Tab Window Cycling - -#### A5.1 ~~Change `wSwitchPanelHandleEvent` signature from `XEvent*` to `WMEvent*`~~ ✅ -- **Files**: `src/switchpanel.c:684`, `src/switchpanel.h:33` -- **Operation**: - 1. Change function signature from `WWindow *wSwitchPanelHandleEvent(WSwitchPanel *panel, XEvent *event)` to `WWindow *wSwitchPanelHandleEvent(WSwitchPanel *panel, WMEvent *event)` - 2. Inside the function, replace all `event->xmotion.x` field accesses with `event->u.motion.x` (the backend-neutral WMEvent fields) - 3. Replace all `event->xbutton.button` with `event->u.button.button`, etc. - 4. Replace `event->type == MotionNotify` with `event->type == WME_MOTION`, etc. -- **Blocked by**: nothing - -#### A5.2 ~~Remove XEvent synthesis in `cycling.c`~~ ✅ -- **File**: `src/cycling.c:227-259` -- **Operation**: After A5.1, the `wSwitchPanelHandleEvent` takes `WMEvent*`. Delete the entire XEvent synthesis block (lines 227-259) and replace with: - ```c - tmp = wSwitchPanelHandleEvent(swpanel, &ev); - ``` - Pass the `WMEvent ev` directly instead of synthesizing an `XEvent`. -- **Xlib calls eliminated**: 5 (XEvent type references) -- **Blocked by**: A5.1 - ---- - -## Phase B: Dock & App Icons - -### B.1 ~~Eliminate `XClearArea` in `appicon.c`~~ ✅ -- **File**: `src/appicon.c:371` -- **Operation**: Replace `XClearArea(dpy, dicon->icon->core->window, 2, 2, 50, ...)` with `wm_backend->window_clear_area(dicon->icon->core->window, 2, 2, 50, ...)` or `wm_backend->frame_paint(...)` to trigger a repaint. Check if a `window_clear_area` vtable slot exists; if not, use `frame_paint` on the icon's frame. -- **Xlib calls eliminated**: 1 -- **Blocked by**: nothing - -### B.2 ~~Eliminate `USE_DOCK_XDND` guards in `appicon.c`~~ ✅ (commit 3d10085) -**Status**: DONE — dock_shadow frame_buf created for icon drag ghost overlay. All vtable ops (window_move, map, unmap, set_background_pixmap) already handle frame_bufs. -- **File**: `src/appicon.c` — 4 sites (lines 52, 131, 315, 338) -- **Operation**: The XDND setup and handling code is X11-specific. It should be moved behind a vtable slot: - 1. Lines 52, 131: Move `#include "xdnd.h"` and `wXDNDMakeAwareness()` call into the X11 backend's `appicon_show` implementation - 2. Lines 315, 338: The DND event handling in `iconDblClick` / `iconMouseDown` should dispatch through `wm_backend->xdnd_process_client_message` (already exists) - 3. Remove all `#ifdef USE_DOCK_XDND` from `appicon.c` -- **Xlib calls eliminated**: 0 (guards only; actual Xlib calls are in `xdnd.c`) -- **Blocked by**: nothing - -### B.3 ~~Change `wDockReceiveDNDDrop` signature from `XEvent*` to `WMEvent*`~~ ✅ -- **Files**: `src/dock.c:2009`, `src/dock.h:97` -- **Operation**: Same pattern as A5.1. Change `int wDockReceiveDNDDrop(WScreen *scr, XEvent *event)` to take `WMEvent*`. Update the single call site in `event.c:782` to pass the `WMEvent` instead of casting `_x11_raw`. -- **Xlib calls eliminated**: 1 `_x11_raw` access in `event.c` -- **Blocked by**: nothing - -### Phase B Wayland Implementation — COMPLETE (2026-05-23) - -All Wayland-specific Phase B tasks from WAYLAND_ROADMAP.md are done: - -| Task | Description | Status | -|------|-------------|--------| -| B2 | Dock icon drag & drop — dock_shadow frame_buf created | ✅ commit 3d10085 | -| B3 | Dock auto-launch on login — pure policy (fork/exec), works as-is | ✅ no code needed | -| B4 | Clip workspace indicator — number + name rendered on clip icon | ✅ commit 3687ff0 | -| B5 | Drawer icon — directional arrow rendered | ✅ commit 9ed1c19 | -| B6 | Icon bounce on launch — `doAppBounce` uses vtable `window_move`, already works | ✅ no code needed | - ---- - -## Phase C: Visual Polish & Animations - -### C.1 ~~Eliminate XOR rubber-band drawing in `moveres.c`~~ ⏭️ (inside `#if 0` — dead code, skip) -- **File**: `src/moveres.c` — 6 Xlib calls (lines 145-151) -- **Operation**: Replace `XSetForeground` + `XDrawLine` calls with `wm_backend->overlay_draw_line()` vtable calls (slot exists at `backend.h:797`): - ```c - // Before: - XSetForeground(dpy, lgc, scr->line_pixel); - XDrawLine(dpy, scr->root_win, lgc, 0, y-1, scr->scr_width, y-1); - // After: - wm_backend->overlay_draw_line(scr, 0, y-1, scr->scr_width, y-1); - ``` - Do this for all 4 `XDrawLine` calls and the `XSetForeground`. -- **Xlib calls eliminated**: 6 -- **Blocked by**: nothing (vtable slots exist, X11 backend implements them as XDrawLine, Wayland backend implements as pixman overlay) - -### C.3 Verify balloon tooltip vtable slots -- **Files**: `src/backend/backend.h` (line 1772: `balloon_show`), `src/balloon.c` -- **Operation**: Confirm `balloon_show` / `balloon_hide` vtable slots exist and are called from `balloon.c`. Confirm `balloon.c` has zero direct Xlib calls (our analysis showed 0). No action needed if clean. -- **Blocked by**: nothing - ---- - -## Phase D: WINGs Widget Porting — COMPLETE (2026-05-23) - -*All WINGs shared widget code now has zero X11 executing on the Wayland path.* - -### Phase D Summary - -| Commit | Files | Description | -|--------|-------|-------------| -| 264679a | wwindow.c | ICCCM properties routed through viewBackend vtable | -| 9eecb25 | wballoon.c, wpopupbutton.c, wtext.c | Drawing routed through render/view backends | -| 2dce454 | wsplitview.c, wtextfield.c | XOR drawing via xor_fill_rect vtable, removed XRotateBuffers | -| 9ccfd33 | selection.c | selection_release + selection_request vtable dispatch | -| 0e26aa8 | wcolorpanel.c | 60 pixmap/copy/fill calls → render backend | -| 8ec3380 | wcolorpanel.c | Magnifier gated via magnify_callback vtable (X11 provides impl, Wayland NULL) | -| 7077e00 | wtext.c, wmisc.c | Removed dead X11 code (unused XEvent, dead #ifndef DOUBLE_BUFFER) | - -### Verified Safe (no changes needed) - -| File | Reason | -|------|--------| -| winputmethod.c | W_InitIM called from W_X11PlatformInit only; W_CreateIC/W_DestroyIC guarded by scr->imctx (NULL on Wayland) | -| wappresource.c | W_InitApplication called from W_X11PlatformInit only | -| wballoon.c (drawing) | drawBalloon/makePixmap/W_BalloonShowText only reachable via x11_balloon_draw vtable | -| DnD (dragcommon/dragsource/dragdestination) | drag_begin vtable intercepts WMDragImageFromView; all other DnD code only called from X11 event handlers | -| wtextfield.c convertSelection | X11 selection handler, only triggered by SelectionRequest X events | -| wcolorpanel.c magnifier | 40 X11 calls gated behind magnify_callback (NULL on Wayland) | - -### Remaining X11 References (compile-time only, no runtime impact) - -- `XPoint` struct type in wruler.c, wtabview.c (just 2 ints) -- `XSetWindowAttributes` in wview.c (struct type for view init) -- `#include ` for key constants (xkbcommon uses same values) -- `WMCreatePixmapFromXPixmaps` API name in wbutton.c, wcolorwell.c - ---- - -### D.1 Route `widgets.c` screen init through backends -- **File**: `WINGs/widgets.c` — 35 Xlib calls -- **Operation**: The screen initialization code (lines 568-739) creates GCs, cursors, stipple pixmaps, and interns atoms using raw Xlib. This code runs on **both** backends but should only run on X11. Split into two parts: - 1. **Lines 568-571**: `XOpenDisplay` — already handled (WINGs init takes a `Display*` or NULL) - 2. **Lines 637-739**: GC creation, cursor creation, modifier mapping, atom interning. Wrap this entire block in: - ```c - if (scrPtr->display != NULL) { // X11 path - // existing code - } - ``` - The Wayland path skips GC/cursor creation (handled by `wrender_wl.c` and `wview_wl.c`). The modifier mapping should use `xkb_keymap` on Wayland (already in `wevent_wl.c`). - 3. **Lines 735-739**: `XInternAtoms` — move to a `viewBackend->platform_init()` call (slot exists at `backend.h` line 621, already called at `widgets.c:832`) -- **Xlib calls eliminated**: 35 -- **Blocked by**: nothing - -### D.2 Route `wwindow.c` ICCCM properties through vtable -- **File**: `WINGs/wwindow.c` — 41 Xlib calls -- **Operation**: Every Xlib call sets ICCCM window properties (`WM_NAME`, `WM_ICON_NAME`, `WM_PROTOCOLS`, `WM_NORMAL_HINTS`, `WM_CLASS`, `WM_HINTS`, `_NET_WM_ICON`, transient-for). These are X11-only concepts. For each function: - 1. `WMSetWindowTitle` (line 155): Wrap the `XmbTextListToTextProperty` + `XSetWMName` + `XChangeProperty` block in `if (scr->display)`. On Wayland, set `xdg_toplevel_set_title` via a new `viewBackend->set_title(view, title)` slot. - 2. `WMSetWindowMiniwindowTitle` (line 176): Same pattern, `viewBackend->set_icon_title(view, title)`. - 3. `WMSetWindowMiniwindowImage` (line 230): `XChangeProperty` for `_NET_WM_ICON`. Wrap in `if (scr->display)`. No Wayland equivalent needed (compositor owns icons). - 4. `WMSetWindowCloseAction` / `WMSetWindowMaximized` (lines 260-285): `XGetWMProtocols` / `XSetWMProtocols`. Wrap in `if (scr->display)`. - 5. `setWindowGeometry` (line 318): `XAllocSizeHints` / `XSetWMNormalHints`. Wrap in `if (scr->display)`. - 6. `WMRealizeWindow` (lines 415-464): `XAllocClassHint` / `XSetClassHint` / `XAllocWMHints` / `XSetWMHints` / `XSetWMProtocols` / `XSetTransientForHint`. Wrap in `if (scr->display)`. - 7. `WMSetWindowMiniwindowPixmap` (line 572): `XGetWMHints` / `XSetWMHints`. Wrap in `if (scr->display)`. - 8. `WMCloseWindow` (line 618): `XWithdrawWindow`. Replace with `viewBackend->unmap(vctx, view)`. -- **Xlib calls eliminated**: 41 -- **Blocked by**: nothing - -### D.3 Move `winputmethod.c` behind a backend vtable -- **File**: `WINGs/winputmethod.c` — 40 Xlib calls -- **Operation**: The entire file is XIM/XIC lifecycle management — purely X11. On Wayland, input methods use `zwp_text_input_v3`. Create two files: - 1. Rename `winputmethod.c` → `winputmethod_x11.c` - 2. Create `winputmethod_wl.c` with stub implementations (or `zwp_text_input_v3` if needed now) - 3. Create `winputmethod.c` as a thin dispatch layer that calls `scr->imBackend->*()` or uses `if (scr->display)` guards - 4. Update `Makefile.am` to compile all three -- **Xlib calls eliminated**: 40 (moved to `_x11.c`) -- **Blocked by**: nothing - -### D.4 Move `wballoon.c` drawing through render backend -- **File**: `WINGs/wballoon.c` — 28 Xlib calls -- **Operation**: The balloon drawing code uses `XFillArc`, `XFillRectangle`, `XFillPolygon`, `XSetForeground`, `XCreateGC` to draw shaped tooltips. Replace each with `renderBackend->fill_arc()`, `renderBackend->fill_rect()`, `renderBackend->fill_polygon()` calls. The render backend vtable already has these slots. - - `XTranslateCoordinates` (line 182): Replace with `viewBackend->get_screen_position()` (slot exists). - - `USE_XSHAPE` guards (2 sites): Move shape extension calls into `viewBackend` or wrap in `if (scr->display)`. -- **Xlib calls eliminated**: 28 -- **Blocked by**: nothing - -### D.5 Move `selection.c` behind a backend vtable -- **File**: `WINGs/selection.c` — 21 Xlib calls -- **Operation**: The entire file implements X11 selection protocol (`XConvertSelection`, `XGetWindowProperty`, `XChangeProperty`, `XSendEvent`, `XSetSelectionOwner`). On Wayland, clipboard uses `wl_data_device`. Create: - 1. Rename `selection.c` → `selection_x11.c` - 2. Create `selection_wl.c` with `wl_data_device` implementation (or stubs) - 3. Create `selection.c` dispatch layer - 4. Update `Makefile.am` -- **Xlib calls eliminated**: 21 (moved to `_x11.c`) -- **Blocked by**: nothing - -### D.6 Move DND subsystem behind a backend vtable -- **Files**: `WINGs/dragsource.c` (45 calls), `WINGs/dragdestination.c` (37 calls), `WINGs/dragcommon.c` (16 calls) — 98 total -- **Operation**: The entire XDND protocol implementation is X11-only. On Wayland, DnD uses `wl_data_device` for cross-client and compositor-internal mechanisms for WINGs↔WINGs. - 1. Rename all three to `*_x11.c` - 2. Create `dragsource_wl.c`, `dragdestination_wl.c`, `dragcommon_wl.c` (stubs initially) - 3. Create thin dispatch files or add a `W_DnDBackend` vtable to `WINGsP.h.in` - 4. Update `Makefile.am` -- **Xlib calls eliminated**: 98 (moved to `_x11.c`) -- **Blocked by**: nothing - -### D.7 Route `wcolorpanel.c` through render/view backends -- **File**: `WINGs/wcolorpanel.c` — 89 Xlib calls -- **Operation**: The color panel does raw pixmap manipulation (XCreatePixmap, XCopyArea, XGetImage, XGetPixel, XPutPixel, XPutImage), pointer grabs (XGrabPointer, XUngrabPointer, XQueryPointer), and cursor manipulation (XRecolorCursor, XCreatePixmapCursor). Replace systematically: - 1. **Pixmap ops** (XCreatePixmap, XCopyArea, XFreePixmap, XFillRectangle — ~44 calls): Replace with `renderBackend->pixmap_create()`, `renderBackend->pixmap_copy()`, `renderBackend->pixmap_free()`, `renderBackend->fill_rect()`. - 2. **Image capture** (XGetImage, XGetPixel, XPutPixel, XSubImage, XPutImage, XDestroyImage — ~15 calls): Replace with `renderBackend` image access functions or wrlib `RImage` operations. - 3. **Pointer grabs** (XGrabPointer, XUngrabPointer, XQueryPointer — ~6 calls): Replace with `viewBackend` pointer grab functions. - 4. **Cursor** (XRecolorCursor, XCreatePixmapCursor, XFreeCursor — ~4 calls): Replace with `viewBackend->set_cursor()`. - 5. **Event polling** (XPending, XPeekEvent, XNextEvent, XCheckTypedEvent, XFlush — ~6 calls): Replace with `eventBackend` functions. - 6. **`USE_XSHAPE` guards** (6 sites): Move shape calls into `viewBackend` or guard with `if (scr->display)`. -- **Xlib calls eliminated**: 89 -- **Blocked by**: D.1 (needs render backend working) - -### D.8 Route `wtext.c` clipboard and grabs through backends -- **File**: `WINGs/wtext.c` — 23 Xlib calls -- **Operation**: - 1. **Clipboard** (`XFetchBuffer`, `XStoreBuffer`, `XFree` — 6 calls): Replace with WINGs selection API (which will be backend-split after D.5). - 2. **Pointer grabs** (`XGrabPointer`, `XUngrabPointer` — 4 calls): Replace with `viewBackend` pointer grab. - 3. **Misc** (`XBell` — 2 calls): Replace with `viewBackend->bell()` or no-op. - 4. **Drawing** (`XCreateGC`, `XCreateBitmapFromData`, `XChangeWindowAttributes`, `XSetWindowAttributes`, `XMapWindow`, `XInternAtom`, `XFlush` — 9 calls): Route through render/view backends. - 5. **`XRectangle` type** (2 uses): Replace with `WMRect`. -- **Xlib calls eliminated**: 23 -- **Blocked by**: D.5 (clipboard) - -### D.9 Route `wtextfield.c` clipboard and grabs through backends -- **File**: `WINGs/wtextfield.c` — 10 Xlib calls -- **Operation**: Same categories as D.8 but smaller: - 1. Clipboard (`XFetchBuffer`, `XStoreBuffer`, `XFree`): Use WINGs selection API - 2. Grabs (`XGrabPointer`, `XUngrabPointer`): Use `viewBackend` - 3. Bell (`XBell`): Use `viewBackend->bell()` or no-op - 4. Drawing (`XDrawRectangle`, `XInternAtom`, `XFlush`): Use `renderBackend` -- **Xlib calls eliminated**: 10 -- **Blocked by**: D.5 - -### D.10 Route `wpopupbutton.c` grabs through view backend -- **File**: `WINGs/wpopupbutton.c` — 7 Xlib calls -- **Operation**: Replace `XGrabPointer` / `XUngrabPointer` / `XFlush` with `viewBackend` pointer grab calls. -- **Xlib calls eliminated**: 7 -- **Blocked by**: nothing - -### D.11 Route `wappresource.c` property reads through backend -- **File**: `WINGs/wappresource.c` — 15 Xlib calls -- **Operation**: Replace `XGetWindowProperty` / `XFree` / `XInternAtom` calls with `viewBackend` property functions or guard with `if (scr->display)`. These read app icon properties — on Wayland, icons come from `xdg_toplevel` or desktop files. -- **Xlib calls eliminated**: 15 -- **Blocked by**: nothing - -### D.12 Route `wmisc.c` coordinate translation through view backend -- **File**: `WINGs/wmisc.c` — 2 Xlib calls -- **Operation**: Replace `XTranslateCoordinates` with `viewBackend->get_screen_position()`. -- **Xlib calls eliminated**: 2 -- **Blocked by**: nothing - ---- - -## Phase E: Dialogs & Panels - -### E.1 Eliminate `_x11_raw` in `winspector.c` -- **File**: `src/winspector.c:1038` -- **Operation**: The `_x11_raw` cast is used to get an `XEvent*` for some event handling. Replace with `WMEvent` field access (same pattern as A5.1). -- **Xlib calls eliminated**: 1 `_x11_raw` access -- **Blocked by**: nothing - -### E.2 Eliminate `_x11_raw` accesses in `event.c` -- **File**: `src/event.c` — 16 `_x11_raw` sites -- **Operation**: Systematic elimination, one site at a time: - - | Line(s) | Context | Replacement | - |---|---|---| - | 249, 264, 269, 276 | `handleConfigureRequest` — passes raw XEvent to backend for RandR/Shape/XKB | Already dispatches through `wm_backend->event_randr_update()` and `wm_backend->event_handle_extensions()`. The `_x11_raw` is the *argument* to these vtable calls. Change vtable signatures to accept `WMEvent*` instead of `void*`, and have the X11 backend extract `_x11_raw` internally. | - | 431 | `handleMapRequest` — raw XEvent for `wManageWindow` | Check if `wManageWindow` actually needs the raw event or just the window ID (from `event->u.mapRequest.window`). If just the ID, pass that. | - | 446-447 | `handleExtensions` — passes raw event to backend | Already a vtable call. Change vtable to accept `WMEvent*`. | - | 782 | `handleButtonPress` — raw XEvent for `wDockReceiveDNDDrop` | Eliminated by task B.3. | - | 831, 834, 852, 857 | `handleClientMessage` — raw XEvent for EWMH, XDND, selection | Already dispatched through `wm_backend->netwm_process_client_message()` and `wm_backend->xdnd_process_client_message()`. Change vtable signatures to accept `WMEvent*`. | - | 971, 974 | `handleClientMessage` — same | Same as above. | - | 993 | `handleClientMessage` — colormap install | Change vtable call to accept `WMEvent*`. | - | 1354 | `handleKeyPress` — raw XEvent for root menu shortcuts | Task E.3 below. | - -- **Xlib calls eliminated**: 16 `_x11_raw` accesses -- **Blocked by**: B.3, E.3 - -### E.3 ~~Change `wRootMenuPerformShortcut` from `XEvent*` to `WMEvent*`~~ ✅ -- **Files**: `src/rootmenu.c:320`, `src/rootmenu.h:25` -- **Operation**: Change signature from `Bool wRootMenuPerformShortcut(XEvent *event)` to `Bool wRootMenuPerformShortcut(WMEvent *event)`. Update internals to use `event->u.key.*` fields instead of `event->xkey.*`. -- **Blocked by**: nothing - -### E.4 ~~Eliminate X11 types in `client.c` function signatures~~ ✅ -- **Files**: `src/client.c:119,247`, `src/client.h:27,31` -- **Operation**: - 1. `wClientConfigure(WWindow *wwin, XConfigureRequestEvent *xcre)` → change parameter to `WMEvent *event` and extract fields from `event->u.configureRequest.*` - 2. `wClientCheckProperty(WWindow *wwin, XPropertyEvent *event)` → change parameter to `WMEvent *event` and extract from `event->u.property.*` - 3. Update all call sites (in `event.c`) -- **Xlib calls eliminated**: 3 (XWindowChanges usage inside, plus signature cleanup) -- **Blocked by**: nothing - -### E.5 Port WPrefs.app drawing through WINGs render backend -- **Files**: `WPrefs.app/Appearance.c` (67 calls), `WPrefs.app/editmenu.c` (37), `WPrefs.app/Menu.c` (24), `WPrefs.app/TexturePanel.c` (14), `WPrefs.app/HotCornerShortcuts.c` (11), `WPrefs.app/KeyboardShortcuts.c` (12), `WPrefs.app/MouseSettings.c` (6), `WPrefs.app/WPrefs.c` (7), `WPrefs.app/main.c` (4), `WPrefs.app/double.c` (2), `WPrefs.app/Paths.c` (1), `WPrefs.app/xmodifier.c` (12) -- **Operation**: WPrefs.app is a WINGs client. All its Xlib calls should go through WINGs APIs: - - | Sub-task | Files | Calls | Replacement | - |---|---|---|---| - | E.5a: Replace `XCopyArea`/`XFillRectangle`/`XDrawLine`/`XDrawRectangle`/`XFillPolygon`/`XDrawLines` with `WMDrawLine`, `WMFillRectangle`, `WMCopyArea` (WINGs drawing API, routed through `renderBackend`) | `Appearance.c`, `Menu.c`, `TexturePanel.c`, `HotCornerShortcuts.c`, `double.c`, `Paths.c`, `KeyboardShortcuts.c` | 120 | WINGs render API | - | E.5b: Replace `XCreatePixmap`/`XFreePixmap` with `WMCreatePixmap`/`WMReleasePixmap` | `Appearance.c`, `HotCornerShortcuts.c`, `TexturePanel.c` | 16 | WINGs pixmap API | - | E.5c: Replace `XCreateGC`/`XFreeGC` with WINGs managed GCs or remove (render backend handles GCs internally) | `Appearance.c`, `Menu.c` | 6 | Remove; use WINGs color GCs | - | E.5d: Replace `XGrabKeyboard`/`XUngrabKeyboard` with WINGs keyboard grab API (needs creation or backend call) | `KeyboardShortcuts.c`, `Menu.c` | 4 | New WINGs API or `viewBackend` | - | E.5e: Replace `XGrabPointer`/`XUngrabPointer`/`XQueryPointer`/`XTranslateCoordinates` with WINGs pointer API | `editmenu.c` | 12 | `viewBackend` pointer ops | - | E.5f: Replace `XGetModifierMapping`/`XKeysymToKeycode`/`XKeysymToString` with xkbcommon equivalents or WINGs API | `KeyboardShortcuts.c`, `MouseSettings.c`, `xmodifier.c` | 18 | xkbcommon or WINGs key API | - | E.5g: Replace `XParseColor` with WINGs `WMCreateNamedColor` | `Appearance.c`, `TexturePanel.c` | 3 | WINGs color API | - | E.5h: Replace `XSendEvent`/`XInternAtom` (WM reload command) with WINGs or backend-neutral mechanism | `WPrefs.c` | 3 | Write to defaults file + signal | - | E.5i: Replace `XOpenDisplay` in `main.c` with WINGs init (already takes display) | `main.c` | 4 | WINGs `WMOpenScreen` | - | E.5j: Replace `XChangePointerControl`/`XGetPointerControl` with backend call or libinput | `MouseSettings.c` | 2 | Backend-specific or defer | - | E.5k: Replace `XRecolorCursor`/`XMoveWindow`/`XClearWindow`/`XSetWMNormalHints` etc. in `editmenu.c` | `editmenu.c` | 15 | WINGs view/window API | - -- **Xlib calls eliminated**: 197 total -- **Blocked by**: D.1 through D.10 (WINGs backends must work first) - ---- - -## Phase G: Session & Persistence - -### G.1 Verify `session.c` is X11-free -- **File**: `src/session.c` -- **Operation**: Our analysis showed 0 Xlib calls. Confirm by grep. No action needed. -- **Blocked by**: nothing - -### G.2 Verify `defaults.c` XColor usage -- **File**: `src/defaults.c` — 20 Xlib references (all `XColor` type for color parsing) -- **Operation**: The `XColor` references are for parsing color names from the defaults database. These should use `wm_backend->color_parse_name()` or equivalent vtable slot. Check if a vtable slot exists for color name→RGB conversion. If so, replace the `XColor` + `XParseColor` calls. If not, add one. -- **Xlib calls eliminated**: 20 -- **Blocked by**: nothing - ---- - -## Phase I: EWMH / Foreign Toplevel - -### I.1 Assess `wmspec.c` — no action needed for Wayland -- **File**: `src/wmspec.c` — 65 Xlib calls -- **Operation**: EWMH (`_NET_WM_*`) is an X11-only protocol. On Wayland, the equivalent is `wlr_foreign_toplevel_management_v1` (already advertised by wlroots). The 65 Xlib calls in `wmspec.c` are **correct** — they implement EWMH for X11 clients. They should NOT be eliminated; they should be guarded so they only execute for X11/XWayland clients. -- **Check**: Verify that `wmspec.c` functions are only called from code paths that have an X11 client (XWayland surface). The `if (!dpy || !wwin->frame)` guard at line 2000 suggests some guarding exists. Verify completeness. -- **Xlib calls eliminated**: 0 (these are correct for X11 path) -- **Blocked by**: nothing - -### I.2 Assess `xdnd.c` — no action needed for Wayland -- **File**: `src/xdnd.c` — 33 Xlib calls -- **Operation**: Same as I.1. XDND is X11-only. On Wayland, DnD uses `wl_data_device` (handled by wlroots). The `xdnd.c` code is correct for XWayland clients. Verify it's only called from X11 code paths. -- **Blocked by**: nothing - ---- - -## Phase H: Advanced Compositor Features - -### H.1 Eliminate `USE_XINERAMA` guards in `src/` policy files -- **Files**: `src/actions.c:975`, `src/wmspec.c` (6 sites), `src/workspace.c` (5 sites — covered in A4.1) -- **Operation**: Same pattern as A4.1. Replace Xinerama-specific monitor queries with backend-neutral `wGetHeadForPoint()` / `wGetUsableAreaForHead()` calls. These functions already exist and are backend-abstracted (the backend populates `WScreen` head geometry). -- **Xlib calls eliminated**: 0 (compile-time guards) -- **Blocked by**: nothing - -### H.5 Verify clipboard works via wlroots -- **Operation**: wlroots implements `wl_data_device` and `zwp_primary_selection_v1`. Verify copy/paste works between native Wayland clients and between XWayland clients. No code changes expected. -- **Blocked by**: nothing - ---- - -## Remaining `src/` Cleanup (not phase-specific) - -### R.1 ~~Eliminate `_x11_raw` in `framewin.c`~~ ✅ -- **File**: `src/framewin.c:1165` -- **Operation**: The `_x11_raw` access is in a titlebar click handler. Replace with `WMEvent` field access. -- **Blocked by**: nothing - -### R.2 ~~Eliminate `_x11_raw` in `icon.c`~~ ✅ -- **File**: `src/icon.c:861` -- **Operation**: The `_x11_raw` access is in `miniwindowDblClick`. Replace with `WMEvent` field access. -- **Blocked by**: nothing - -### R.3 ~~Eliminate `_x11_raw` in `menu.c`~~ ✅ -- **File**: `src/menu.c:738` -- **Operation**: The `_x11_raw` access is in `check_key`. Replace with `WMEvent` field access for key handling. -- **Blocked by**: nothing - -### R.4 Eliminate X11 types in `misc.c` -- **File**: `src/misc.c` — 8 Xlib calls (`XKeysymToKeycode`, `XLookupString`, `DefaultRootWindow`) -- **Operation**: Replace with `wm_backend->keysym_to_keycode()` (vtable slot exists) and `wm_backend->key_lookup_string()` (may need new slot). -- **Blocked by**: nothing - -### R.5 ~~Eliminate X11 in `usermenu.c`~~ ✅ -- **File**: `src/usermenu.c` — 4 Xlib calls (`XSendEvent`, `XStringToKeysym`) -- **Operation**: Replace `XSendEvent` with `wm_backend->client_send_key_event()` or equivalent vtable slot. Replace `XStringToKeysym` with `xkb_keysym_from_name()` (libxkbcommon, already linked). -- **Blocked by**: nothing - -### R.6 Move `xmodifier.c` behind backend -- **File**: `src/xmodifier.c` — 11 Xlib calls -- **Operation**: This file detects keyboard modifier mappings. On Wayland, modifiers come from `xkb_keymap`. The X11 path should remain for X11 backend. Guard the entire file with `if (dpy)` or move to `backend/x11/`. -- **Blocked by**: nothing - -### R.7 ~~Eliminate `USE_XSHAPE` guards in `window.c`~~ ✅ -- **File**: `src/window.c` — 4 `#ifdef USE_XSHAPE` sites -- **Operation**: Shape extension queries should be behind the backend. Move `XShapeQueryExtents` calls into `wm_backend->window_get_shape()` or similar vtable slot. The X11 backend implements it; the Wayland backend returns "no shape" (Wayland surfaces handle their own shape via alpha). -- **Blocked by**: nothing - -### R.8 Eliminate `XKB_BUTTON_HINT` guards -- **Files**: `src/framewin.c` (22 sites), `src/window.c` (10), `src/wdefaults.c` (3), `src/framewin.h` (10), `src/window.h` (1), `src/WindowMaker.h` (1), `src/def_pixmaps.h` (1), `src/dialog.c` (2), `src/winspector.c` (1) — **51 sites total** -- **Operation**: The language button feature is controlled by a compile-time `#ifdef`. This is not a backend concern — it's a feature toggle. Two options: - - (a) Make it always-on (remove `#ifdef`, keep the code) - - (b) Make it a runtime preference (remove `#ifdef`, add `wPreferences.show_language_button` check) - - (c) Leave as-is (it's not blocking Wayland) -- **Recommendation**: Option (c) — this is orthogonal to Wayland porting. Skip. - -### R.9 Eliminate `KEEP_XKB_LOCK_STATUS` guards -- **Files**: `src/framewin.c` (2), `src/event.c` (1), `src/window.c` (1), `src/WindowMaker.h` (2), `src/framewin.h` (1), `src/keybind.h` (1) — 8 sites -- **Operation**: Same as R.8 — feature toggle, not backend concern. Skip. - -### R.10 Eliminate `HAVE_INOTIFY` guards -- **Files**: `src/main.c` (3), `src/event.c` (1), `src/shutdown.c` (2), `src/WindowMaker.h` (1) — 7 sites -- **Operation**: Inotify is Linux-specific, not X11-specific. Works on Wayland too. No action needed. - ---- - -## Execution Order Summary - -``` -IMMEDIATE (unblocks everything else): - ~~F.2 Verify RCreateContextWayland~~ ✅ - ~~F.3 Verify wl_convert_image~~ ✅ - ~~F.4 Verify image loaders~~ ✅ - ~~F.5 Wayland-safe XPM loading~~ ✅ - -WAVE 1 (independent, no prerequisites): - ~~A4.1 workspace.c USE_XINERAMA → backend-neutral~~ ✅ - A5.1 ~~switchpanel.c XEvent* → WMEvent*~~ ✅ - ~~B.1 appicon.c XClearArea → vtable~~ ✅ - B.2 ~~appicon.c USE_DOCK_XDND → backend~~ ✅ - B.3 ~~dock.c/h XEvent* → WMEvent*~~ ✅ - ~~C.1 moveres.c XDrawLine → overlay vtable~~ ⏭️ (inside `#if 0` — dead code, skip) - D.1 widgets.c screen init → backend guard - D.3 winputmethod.c → _x11/_wl split - D.5 selection.c → _x11/_wl split - D.6 DND subsystem → _x11/_wl split - D.10 wpopupbutton.c grabs → viewBackend - D.11 wappresource.c props → backend guard - D.12 wmisc.c coords → viewBackend - E.3 ~~rootmenu.c XEvent* → WMEvent*~~ ✅ - E.4 ~~client.c X11 types → WMEvent*~~ ✅ - R.1 ~~framewin.c _x11_raw → WMEvent~~ ✅ - R.2 ~~icon.c _x11_raw → WMEvent~~ ✅ - R.3 ~~menu.c _x11_raw → WMEvent~~ ✅ - R.4 misc.c Xlib → vtable - ~~R.5 usermenu.c Xlib → vtable~~ ✅ - R.6 xmodifier.c → backend guard - ~~R.7 window.c USE_XSHAPE → vtable~~ ✅ - G.2 defaults.c XColor → vtable - I.1 wmspec.c — verify X11-only paths - I.2 xdnd.c — verify X11-only paths - -WAVE 2 (depends on Wave 1): - A5.2 ~~cycling.c XEvent synthesis → delete~~ ✅ - D.2 wwindow.c ICCCM → viewBackend - D.4 wballoon.c drawing → renderBackend - D.7 wcolorpanel.c → renderBackend/viewBackend - D.8 wtext.c clipboard/grabs → backends - D.9 wtextfield.c clipboard/grabs → backends - E.1 winspector.c _x11_raw → WMEvent - E.2 event.c _x11_raw elimination (all 16 sites) - -WAVE 3 (depends on Wave 2): - E.5 WPrefs.app full port (197 calls) - -VERIFY (can happen anytime): - C.3 balloon vtable slots - ~~F.2-F.4~~ wrlib Wayland path ✅ - G.1 session.c clean - H.1 USE_XINERAMA policy cleanup - H.5 clipboard via wlroots -``` - ---- - -## Scorecard - -| Category | Xlib calls to eliminate | Tasks | -|---|---|---| -| `src/` `_x11_raw` escapes | 16 | E.2, R.1-R.3, E.1 | -| `src/` X11 function signatures | ~15 | A5.1, B.3, E.3, E.4 | -| `src/` direct Xlib calls | ~50 | C.1, R.4, R.5, R.6, G.2, B.1 | -| `src/` compile-time guards | ~25 sites | A4.1, B.2, R.7, H.1 | -| `WINGs/` non-split Xlib calls | 412 | D.1-D.12 | -| `WPrefs.app/` Xlib calls | 197 | E.5 | -| `wrlib/` verification | 0 ✅ (all done) | ~~F.2-F.5~~ ✅ | -| `wmlib/` | 9 | Not addressed (too small; keep for X11 or drop) | -| **Total** | **~700** | **~45 tasks** | diff --git a/ACTION_PLAN_PROMPTS.md b/ACTION_PLAN_PROMPTS.md deleted file mode 100644 index 97a88af..0000000 --- a/ACTION_PLAN_PROMPTS.md +++ /dev/null @@ -1,666 +0,0 @@ -# Prompt Templates for Mechanical Wayland Port Tasks - -## Reference Tables (embed in every prompt) - -### X11 Event Type → WMEvent Type - -``` -KeyPress/KeyRelease → WME_KEY_PRESS/WME_KEY_RELEASE → event->u.key.* -ButtonPress/ButtonRelease → WME_BUTTON_PRESS/WME_BUTTON_RELEASE → event->u.button.* -MotionNotify → WME_MOTION → event->u.motion.* -EnterNotify/LeaveNotify → WME_ENTER/WME_LEAVE → event->u.crossing.* -Expose → WME_EXPOSE → event->u.expose.* -ConfigureNotify → WME_CONFIGURE → event->u.configure.* -ConfigureRequest → WME_CONFIGURE_REQUEST → event->u.configureRequest.* -MapNotify → WME_MAP → event->u.lifecycle.* -MapRequest → WME_MAP_REQUEST → event->u.mapRequest.* -UnmapNotify → WME_UNMAP → event->u.lifecycle.* -DestroyNotify → WME_DESTROY → event->u.lifecycle.* -FocusIn/FocusOut → WME_FOCUS_IN/WME_FOCUS_OUT → event->u.focus.* -PropertyNotify → WME_PROPERTY → event->u.property.* -ClientMessage → WME_CLIENT_MESSAGE → event->u.clientMessage.* -SelectionNotify → WME_SELECTION → event->u.selection.* -SelectionRequest → WME_SELECTION_REQUEST → event->u.selection.* (check) -SelectionClear → WME_SELECTION_CLEAR → event->u.selection.* (check) -VisibilityNotify → WME_VISIBILITY → event->u.visibility.* -ColormapNotify → WME_COLORMAP → event->u.colormap.* -MappingNotify → WME_MAPPING → event->u.mapping.* -ReparentNotify → WME_REPARENT → event->u.reparent.* -``` - -### X11 Event Field → WMEvent Field - -``` -event->xkey.keycode → event->u.key.keycode -event->xkey.state → event->u.key.state -event->xkey.time → event->u.key.time -event->xkey.window → event->u.key.window -event->xkey.x, y → event->u.key.x, y -event->xkey.x_root → event->u.key.x_root - -event->xbutton.button → event->u.button.button -event->xbutton.state → event->u.button.state -event->xbutton.time → event->u.button.time -event->xbutton.window → event->u.button.window -event->xbutton.x, y → event->u.button.x, y -event->xbutton.x_root → event->u.button.x_root - -event->xmotion.state → event->u.motion.state -event->xmotion.time → event->u.motion.time -event->xmotion.window → event->u.motion.window -event->xmotion.x, y → event->u.motion.x, y -event->xmotion.x_root → event->u.motion.x_root - -event->xcrossing.window → event->u.crossing.window -event->xcrossing.state → event->u.crossing.state -event->xcrossing.time → event->u.crossing.time -event->xcrossing.mode → event->u.crossing.mode -event->xcrossing.detail → event->u.crossing.detail - -event->xconfigure.window → event->u.configure.window -event->xconfigure.x, y → event->u.configure.x, y -event->xconfigure.width → event->u.configure.width -event->xconfigure.height → event->u.configure.height - -event->xclient.data.l[N] → event->u.clientMessage.data.l[N] -event->xclient.message_type → event->u.clientMessage.atom -event->xclient.window → event->u.clientMessage.window - -event->xproperty.atom → event->u.property.atom -event->xproperty.time → event->u.property.time -event->xproperty.window → event->u.property.window -``` - ---- - -## Template 1: Change Function Signature from `XEvent*` to `WMEvent*` - -**Use for**: A5.1, B.3, E.3, E.4 (and any function that takes an X11 event type as parameter) - -``` -TASK: Change the function {{FUNCTION_NAME}} in {{FILE}} from taking an X11 event -type to taking the backend-neutral WMEvent type. - -GOVERNING PRINCIPLE: Policy code in src/ must never reference X11 types. The -backend-neutral event type is `WMEvent` (defined in WINGs/WINGs/WMEvent.h). - -STEPS: - -1. Read {{FILE}} and {{HEADER_FILE}} to find the function signature. - -2. Change the parameter type: - OLD: {{OLD_PARAM_TYPE}} *{{PARAM_NAME}} - NEW: WMEvent *{{PARAM_NAME}} - -3. Update the header file ({{HEADER_FILE}}) with the same signature change. - -4. Inside the function body, replace every X11 event field access: - - event->xkey.* → event->u.key.* - - event->xbutton.* → event->u.button.* - - event->xmotion.* → event->u.motion.* - - event->xcrossing.* → event->u.crossing.* - - event->xconfigure.* → event->u.configure.* - - event->xclient.* → event->u.clientMessage.* - - event->xproperty.* → event->u.property.* - - event->type == MotionNotify → event->type == WME_MOTION - - event->type == ButtonPress → event->type == WME_BUTTON_PRESS - - event->type == ButtonRelease → event->type == WME_BUTTON_RELEASE - - event->type == KeyPress → event->type == WME_KEY_PRESS - (Use the X11→WMEvent mapping table for the complete list.) - -5. Find ALL call sites by grepping for {{FUNCTION_NAME}} across src/ and WINGs/. - At each call site: - - If the caller has a `WMEvent`, pass it directly. - - If the caller has an `XEvent*` via `_x11_raw`, change to pass the `WMEvent` - directly (this eliminates the `_x11_raw` access). - -6. If the function used `XWindowChanges` or other X11 structs internally to call - Xlib functions, replace those Xlib calls with wm_backend->*() vtable calls - using the WMEvent fields. - -VERIFICATION: -- `grep -rn '{{OLD_PARAM_TYPE}}' {{FILE}} {{HEADER_FILE}}` returns zero matches. -- `grep -rn '_x11_raw.*{{FUNCTION_NAME}}\|{{FUNCTION_NAME}}.*_x11_raw' src/` returns - zero matches. -- The code compiles. - -CONSTRAINTS: -- Do NOT add `#ifdef` guards. Do NOT add `if (dpy)` checks. -- Do NOT change the function's logic — only its interface types. -- Do NOT modify files in src/backend/. -``` - ---- - -## Template 2: Eliminate `_x11_raw` Access - -**Use for**: R.1, R.2, R.3, E.1, and individual sites in E.2 - -``` -TASK: Eliminate the `_x11_raw` escape hatch at {{FILE}}:{{LINE}}. - -GOVERNING PRINCIPLE: Policy code must never reach through the WMEvent abstraction -to access the raw X11 event. The `_x11_raw` field exists only for backend code. - -CONTEXT: At line {{LINE}}, the code does: - XEvent *xev = (XEvent *)event->_x11_raw; -and then accesses xev->{{XEVENT_FIELD}} to get {{WHAT_IT_NEEDS}}. - -STEPS: - -1. Read {{FILE}} around line {{LINE}} (±30 lines) to understand what information - is extracted from the raw XEvent. - -2. Identify the equivalent field in the WMEvent struct: - {{XEVENT_FIELD}} → {{WMEVENT_FIELD}} - (Use the X11→WMEvent field mapping table.) - -3. Replace the `_x11_raw` cast and XEvent field access with direct WMEvent access: - OLD: XEvent *xev = (XEvent *)event->_x11_raw; - ... xev->{{XEVENT_FIELD}} ... - NEW: ... event->{{WMEVENT_FIELD}} ... - -4. Delete the `XEvent *xev` declaration and the cast. - -5. If the raw XEvent was passed to another function (e.g., `someFunc(xev)`), - that function's signature must also be changed (use Template 1). - -6. If the raw XEvent was passed to a `wm_backend->*()` vtable call, change the - vtable call to accept `WMEvent*` instead of `void*`. Update the vtable - signature in backend.h, and update both x11_backend.c and wl_backend.c - implementations. The X11 backend extracts `_x11_raw` internally; the Wayland - backend uses WMEvent fields directly. - -VERIFICATION: -- `grep -n '_x11_raw' {{FILE}}` shows one fewer match than before. -- The code compiles. - -CONSTRAINTS: -- Do NOT add NULL checks on `_x11_raw`. Do NOT add fallback paths. -- If the information needed is NOT available in WMEvent, STOP and report what - field is missing. Do not invent a workaround. -``` - ---- - -## Template 3: Replace Direct Xlib Call with `wm_backend->*()` Vtable Call - -**Use for**: C.1, R.4, R.5, B.1, and any `src/*.c` file with direct Xlib calls - -``` -TASK: Replace the direct Xlib call {{XLIB_FUNCTION}} at {{FILE}}:{{LINE}} with -the backend vtable call {{VTABLE_SLOT}}. - -GOVERNING PRINCIPLE: Policy code in src/ calls display operations exclusively -through `wm_backend->*()`. Direct Xlib calls are forbidden. - -CONTEXT: The global `wm_backend` pointer is declared in src/backend/backend.h: - extern const WMBackend *wm_backend; - -STEPS: - -1. Read {{FILE}} around line {{LINE}} (±10 lines) to understand the Xlib call - and its arguments. - -2. Find the matching vtable slot in src/backend/backend.h: - `grep -n '{{VTABLE_SLOT}}' src/backend/backend.h` - Read the slot's signature and documentation comment. - -3. Replace the Xlib call: - OLD: {{XLIB_FUNCTION}}(dpy, {{ARGS}}); - NEW: wm_backend->{{VTABLE_SLOT}}({{TRANSLATED_ARGS}}); - - Argument translation rules: - - Drop `dpy` (the backend knows its own display connection). - - Drop `GC` arguments (the backend manages its own GCs). - - `scr->root_win` → `scr` (the backend knows the root window). - - X11 `Window` → `WNativeWindow` (same underlying type, no cast needed). - - X11 `Pixmap` → `Pixmap` (same type in both backends). - -4. If the vtable slot does NOT exist, STOP and report: - "Need new vtable slot: {{PROPOSED_SIGNATURE}}" - Do not add the slot yourself. - -5. Remove any now-unused `#include` of X11 headers if this was the last Xlib - call in the file. - -VERIFICATION: -- `grep -n '{{XLIB_FUNCTION}}' {{FILE}}` shows one fewer match. -- The code compiles. - -CONSTRAINTS: -- Do NOT add `#ifdef` guards or `if (dpy)` checks. -- Do NOT modify files in src/backend/. -- One Xlib call per task. Do not batch. -``` - ---- - -## Template 4: Guard X11-Only Code Block in WINGs - -**Use for**: D.1, D.2, D.11, and WINGs files where X11 code must be skipped on Wayland - -``` -TASK: Guard the X11-only code block at {{FILE}}:{{START_LINE}}-{{END_LINE}} so it -only executes when running on the X11 backend. - -GOVERNING PRINCIPLE: WINGs code uses `scr->display != NULL` to detect the X11 -backend. When `scr->display` is NULL, the Wayland backend is active and X11 -calls must be skipped. - -STEPS: - -1. Read {{FILE}} from line {{START_LINE}} to {{END_LINE}}. - -2. Confirm every line in this range calls Xlib functions or uses X11 types that - have no meaning on Wayland (GC, XCreateGC, XInternAtom, etc.). - -3. Wrap the block: - ```c - if (scr->display) { - // ... existing X11 code unchanged ... - } - ``` - Where `scr` is the `W_Screen*` in scope. Find it via: - - Direct variable `scr` or `scrPtr` - - `view->screen` - - `WMWidgetScreen(widget)` - -4. If the Wayland backend needs equivalent behavior (e.g., the code creates - cursors or interns atoms), add an `else` block that calls the appropriate - backend vtable: - ```c - if (scr->display) { - // X11 path - } else { - // Wayland path — use scr->viewBackend->*() or scr->renderBackend->*() - } - ``` - If no Wayland equivalent is needed (e.g., GC creation), the `else` is omitted. - -VERIFICATION: -- The code compiles. -- Running with `WMAKER_USE_WAYLAND=1` does not crash in this code path (the - X11 block is skipped). - -CONSTRAINTS: -- Do NOT move the code to a separate file. This template is for cases where - the X11 code is small (< 50 lines) and inline guarding is cleaner than a split. -- Do NOT delete the X11 code. It must continue to work for the X11 backend. -- The guard condition is always `if (scr->display)`, never `#ifdef WAYLAND`. -``` - ---- - -## Template 5: Split File into `_x11.c` / `_wl.c` Backend Pair - -**Use for**: D.3, D.5, D.6, and any WINGs file that is entirely X11-specific - -``` -TASK: Split {{FILE}} into an X11 backend file, a Wayland backend file, and a -thin dispatch layer. - -GOVERNING PRINCIPLE: WINGs uses five backend vtables (view, event, render, font, -color). Files that are entirely X11-specific should be split into _x11/_wl pairs -following the existing pattern (e.g., wcolor.c / wcolor_x11.c / wcolor_wl.c). - -EXISTING PATTERN (use as reference): -- WINGs/wcolor.c — dispatch layer, calls scr->colorBackend->*() -- WINGs/wcolor_x11.c — X11 implementation, exports W_ColorBackendX11 -- WINGs/wcolor_wl.c — Wayland implementation, exports W_ColorBackendWL - -STEPS: - -1. Read {{FILE}} completely. Identify all public functions (declared in headers). - -2. Rename {{FILE}} → {{FILE_BASE}}_x11.c. Keep all code unchanged. - -3. Create {{FILE_BASE}}_wl.c with stub implementations of every public function. - Each stub should either: - - Implement the Wayland equivalent if straightforward - - Call `wwarning("{{FUNCTION}}: not yet implemented for Wayland")` and return - a safe default (NULL, 0, False) - -4. Create {{FILE_BASE}}.c as a dispatch layer. For each public function: - ```c - ReturnType PublicFunction(args...) { - if (scr->display) - return x11_PublicFunction(args...); - else - return wl_PublicFunction(args...); - } - ``` - OR, if a vtable is more appropriate, define a new backend struct in WINGsP.h.in - following the existing pattern (W_ColorBackend, W_FontBackend, etc.). - -5. Update WINGs/Makefile.am: - - Remove {{FILE_BASE}}.c from the source list (it was renamed) - - Add {{FILE_BASE}}.c, {{FILE_BASE}}_x11.c, {{FILE_BASE}}_wl.c - -6. Update any header files that declare the public functions — no signature - changes needed. - -VERIFICATION: -- `make -C WINGs` compiles successfully. -- The X11 backend path is unchanged (existing behavior preserved). -- `grep -rn 'X[A-Z][a-z]' WINGs/{{FILE_BASE}}.c` returns zero (dispatch layer - has no Xlib calls). - -CONSTRAINTS: -- The _wl.c stubs may be incomplete. That is acceptable. Mark incomplete stubs - with `/* TODO: Wayland implementation */`. -- Do NOT change any function signatures. -- Do NOT modify {{FILE_BASE}}_x11.c beyond the rename. All existing code stays. -``` - ---- - -## Template 6: Replace Xlib Drawing Call with WINGs Render Backend - -**Use for**: D.4, D.7, D.8, D.9, D.10, and WPrefs.app drawing (E.5a-c) - -``` -TASK: Replace the Xlib drawing call {{XLIB_CALL}} at {{FILE}}:{{LINE}} with a -WINGs render backend call. - -GOVERNING PRINCIPLE: Widget code draws through `scr->renderBackend->*()`. Direct -Xlib drawing calls (XFillRectangle, XDrawLine, XCopyArea, etc.) are forbidden -in non-_x11.c files. - -RENDER BACKEND VTABLE (from WINGsP.h.in): - scr->renderBackend->pixmap_create(rctx, width, height, depth) - scr->renderBackend->pixmap_free(rctx, pm) - scr->renderBackend->pixmap_copy(rctx, src, dst, sx, sy, w, h, dx, dy) - scr->renderBackend->draw_line(rctx, d, color, x1, y1, x2, y2) - scr->renderBackend->draw_rect(rctx, d, color, x, y, w, h) - scr->renderBackend->fill_rect(rctx, d, color, x, y, w, h) - scr->renderBackend->fill_polygon(rctx, d, color, points, npoints) - scr->renderBackend->fill_arc(rctx, d, color, x, y, w, h, angle1, angle2) - scr->renderBackend->composite_pixmap(rctx, dst, src, sx, sy, w, h, dx, dy) - -Where: - rctx = scr->renderBackendCtx - d = the target WMDrawable (Pixmap or window's drawable) - color = a WMColor* (obtain via WMColorGC pattern → WMColor*) - -STEPS: - -1. Read {{FILE}} around line {{LINE}} (±5 lines). - -2. Identify the Xlib call and its arguments. - -3. Map to the render backend equivalent: - XFillRectangle(dpy, d, gc, x, y, w, h) - → scr->renderBackend->fill_rect(scr->renderBackendCtx, d, color, x, y, w, h) - - XDrawLine(dpy, d, gc, x1, y1, x2, y2) - → scr->renderBackend->draw_line(scr->renderBackendCtx, d, color, x1, y1, x2, y2) - - XDrawRectangle(dpy, d, gc, x, y, w, h) - → scr->renderBackend->draw_rect(scr->renderBackendCtx, d, color, x, y, w, h) - - XCopyArea(dpy, src, dst, gc, sx, sy, w, h, dx, dy) - → scr->renderBackend->pixmap_copy(scr->renderBackendCtx, src, dst, sx, sy, w, h, dx, dy) - - XCreatePixmap(dpy, drawable, w, h, depth) - → scr->renderBackend->pixmap_create(scr->renderBackendCtx, w, h, depth) - - XFreePixmap(dpy, pm) - → scr->renderBackend->pixmap_free(scr->renderBackendCtx, pm) - - XFillPolygon(dpy, d, gc, points, npoints, shape, mode) - → scr->renderBackend->fill_polygon(scr->renderBackendCtx, d, color, points, npoints) - - XFillArc(dpy, d, gc, x, y, w, h, angle1, angle2) - → scr->renderBackend->fill_arc(scr->renderBackendCtx, d, color, x, y, w, h, angle1, angle2) - -4. For the `color` argument: the Xlib code uses a GC with a foreground color. - Find where the GC's foreground was set (look for `XSetForeground` or - `WMColorGC(someColor)`). Use that `WMColor*` directly. - -5. For `scr`: find the WMScreen in scope. Common patterns: - - `WMWidgetScreen(widget)` - - `view->screen` - - local variable `scr` - -VERIFICATION: -- `grep -n '{{XLIB_CALL}}' {{FILE}}` shows one fewer match. -- The code compiles. - -CONSTRAINTS: -- One call replacement per task. -- If the GC was created with `XCreateGC` just for this drawing sequence, and - this is the last use, also delete the `XCreateGC` and `XFreeGC` calls. -- Do NOT change drawing logic (coordinates, colors, order). -``` - ---- - -## Template 7: Remove Compile-Time `#ifdef` Guard Using Backend-Neutral API - -**Use for**: A4.1, B.2, H.1, R.7, and any `#ifdef USE_XINERAMA` / `#ifdef USE_XSHAPE` / `#ifdef USE_DOCK_XDND` site - -``` -TASK: Remove the compile-time guard `#ifdef {{GUARD_NAME}}` at {{FILE}}:{{LINE}} -and replace the guarded code with a backend-neutral API call. - -GOVERNING PRINCIPLE: No `#ifdef` guards for backend-specific features in policy -code. Feature availability is determined at runtime by the backend vtable. - -STEPS: - -1. Read {{FILE}} around line {{LINE}}. Identify the `#ifdef {{GUARD_NAME}}` block - and the corresponding `#else` / `#endif`. - -2. Understand what the guarded code does: - - `USE_XINERAMA`: queries per-monitor geometry - - `USE_XSHAPE`: queries window shape - - `USE_DOCK_XDND`: sets up XDND awareness - - `USE_MWM_HINTS`: reads Motif hints - - `USE_RANDR`: handles RandR events - -3. Identify the backend-neutral replacement: - - Xinerama monitor queries → `wGetHeadForPoint(scr, point)`, - `wGetUsableAreaForHead(scr, head)`, `wGetRectForHead(scr, head)`. - These functions already exist and are backend-abstracted. - - XShape queries → `wm_backend->window_get_shape()` or equivalent vtable slot. - - XDND setup → move into the backend's `appicon_show` implementation. - - MWM hints → `wm_backend->client_get_mwm_hints()` or equivalent vtable slot. - -4. Replace the `#ifdef` / `#else` / `#endif` with the backend-neutral code. - Delete both the guarded block and the else block. Write one unified path. - -5. If the `#ifdef` block called Xlib functions that have no vtable equivalent, - STOP and report: "Need vtable slot for {{OPERATION}}". - -VERIFICATION: -- `grep -n '{{GUARD_NAME}}' {{FILE}}` shows one fewer match. -- The code compiles with and without the feature flag defined. - -CONSTRAINTS: -- Do NOT add a different `#ifdef`. Do NOT add runtime `if (dpy)` checks. -- The replacement must work on both X11 and Wayland backends. -- If the `#else` block was empty (feature simply absent), the replacement is - to call the vtable slot unconditionally — the Wayland backend returns a - sensible default (e.g., "no shape", "single head"). -``` - ---- - -## Template 8: Replace Xlib Call in WPrefs.app with WINGs API - -**Use for**: E.5 sub-tasks (all WPrefs.app Xlib elimination) - -``` -TASK: Replace the Xlib call {{XLIB_CALL}} at WPrefs.app/{{FILE}}:{{LINE}} with -the equivalent WINGs public API call. - -GOVERNING PRINCIPLE: WPrefs.app is a WINGs client application. It must use only -WINGs public APIs (declared in WINGs/WINGs/WINGs.h), never Xlib directly. WINGs -routes all operations through its backend vtables internally. - -WINGS API EQUIVALENTS: - XFillRectangle(dpy, d, WMColorGC(c), x, y, w, h) - → WMFillRectangle(scr, d, c, x, y, w, h) - (if WMFillRectangle does not exist, use: - scr->renderBackend->fill_rect(scr->renderBackendCtx, d, c, x, y, w, h)) - - XDrawLine(dpy, d, WMColorGC(c), x1, y1, x2, y2) - → WMDrawLine(scr, d, c, x1, y1, x2, y2) - (if WMDrawLine does not exist, use: - scr->renderBackend->draw_line(scr->renderBackendCtx, d, c, x1, y1, x2, y2)) - - XCopyArea(dpy, src, dst, gc, sx, sy, w, h, dx, dy) - → WMCopyArea(scr, src, dst, sx, sy, w, h, dx, dy) - (if WMCopyArea does not exist, use: - scr->renderBackend->pixmap_copy(scr->renderBackendCtx, src, dst, sx, sy, w, h, dx, dy)) - - XCreatePixmap(dpy, parent, w, h, depth) - → WMCreatePixmap(scr, w, h, depth, False) [returns WMPixmap*] - then use WMGetPixmapXID(pixmap) to get the drawable - - XFreePixmap(dpy, pm) - → WMReleasePixmap(pixmap) [if using WMPixmap*] - or scr->renderBackend->pixmap_free(scr->renderBackendCtx, pm) - - XCreateGC / XFreeGC - → DELETE. WINGs manages GCs internally. Use WMColor* for color selection. - - XParseColor(dpy, cmap, name, &xcolor) - → WMCreateNamedColor(scr, name, True) [returns WMColor*] - - XGrabKeyboard / XUngrabKeyboard - → No direct WINGs equivalent. Use: - scr->viewBackend->keyboard_grab(scr->viewBackendCtx, view) - (if slot exists; otherwise STOP and report) - - XGrabPointer / XUngrabPointer - → No direct WINGs equivalent. Use: - scr->viewBackend->pointer_grab(scr->viewBackendCtx, view) - (if slot exists; otherwise STOP and report) - - XOpenDisplay - → WMOpenScreen(display_name) [in main.c initialization] - - XSendEvent (for WM commands) - → Write to defaults database + send SIGHUP, or use a WINGs notification. - STOP and report if unclear. - -STEPS: - -1. Read WPrefs.app/{{FILE}} around line {{LINE}} (±10 lines). -2. Identify the Xlib call and its purpose. -3. Find the WINGs equivalent from the table above. -4. Replace the call. Adjust variable types if needed (e.g., `GC gc` → delete, - `XColor xcolor` → `WMColor *color`). -5. If the WINGs API function does not exist, use the renderBackend/viewBackend - vtable call directly (WPrefs.app includes WINGsP.h for internal access). - -VERIFICATION: -- `grep -n '{{XLIB_CALL}}' WPrefs.app/{{FILE}}` shows one fewer match. -- The code compiles. - -CONSTRAINTS: -- One call replacement per task. -- Prefer WINGs public API over direct vtable access. -- Do NOT add `#ifdef` guards or `if (dpy)` checks. -``` - ---- - -## Template 9: Verify File is X11-Free - -**Use for**: F.2, F.3, F.4, G.1, I.1, I.2, C.3, H.5, and any verification task - -``` -TASK: Verify that {{FILE}} has no X11 dependencies that would break on the -Wayland backend. - -STEPS: - -1. Run: `grep -nE '\bX[A-Z][a-z]' {{FILE}}` - This finds direct Xlib function calls. - EXPECTED: {{EXPECTED_XLIB_COUNT}} matches. - -2. Run: `grep -nE '\bDisplay\b|\bXEvent\b|\bXImage\b|\bXColor\b' {{FILE}}` - This finds X11 type usage. - EXPECTED: {{EXPECTED_TYPE_COUNT}} matches. - -3. Run: `grep -n '_x11_raw' {{FILE}}` - This finds raw event escape hatches. - EXPECTED: 0 matches. - -4. Run: `grep -n '#ifdef.*USE_X\|#ifdef.*HAVE_X\|#ifdef.*XINERAMA\|#ifdef.*XSHAPE\|#ifdef.*XDND' {{FILE}}` - This finds X11 feature guards. - EXPECTED: {{EXPECTED_IFDEF_COUNT}} matches. - -5. If any count exceeds expected: - - For each unexpected match, report the line number and the X11 reference. - - Classify as: (a) actual dependency, (b) comment/string, (c) type used - but never passed to Xlib. - -REPORT FORMAT: -``` -FILE: {{FILE}} -Xlib calls: N (expected {{EXPECTED_XLIB_COUNT}}) -X11 types: N (expected {{EXPECTED_TYPE_COUNT}}) -_x11_raw: N (expected 0) -#ifdef guards: N (expected {{EXPECTED_IFDEF_COUNT}}) -STATUS: CLEAN / NEEDS WORK -ISSUES: (list any unexpected findings) -``` - -CONSTRAINTS: -- Do NOT modify the file. This is a read-only verification task. -- Report findings exactly. Do not interpret or fix. -``` - ---- - -## Template 10: Batch Instantiation Helper - -**Use for**: Generating concrete prompts from templates + the action plan - -``` -Given the action plan task {{TASK_ID}}, instantiate the appropriate template. - -ACTION PLAN TASK: - ID: {{TASK_ID}} - File(s): {{FILES}} - Operation: {{OPERATION_SUMMARY}} - Xlib calls to eliminate: {{COUNT}} - Blocked by: {{DEPENDENCIES}} - -Select the template: -- Function signature change (XEvent* → WMEvent*) → Template 1 -- _x11_raw elimination → Template 2 -- Direct Xlib → wm_backend vtable → Template 3 -- Guard X11 block in WINGs → Template 4 -- Split into _x11/_wl pair → Template 5 -- Xlib drawing → WINGs render backend → Template 6 -- Remove #ifdef guard → Template 7 -- WPrefs.app Xlib → WINGs API → Template 8 -- Verification only → Template 9 - -Fill in ALL template variables from the action plan data and the codebase. -Include exact line numbers (use `grep -n` to find current line numbers, as they -may have shifted from the original analysis). - -Output the fully instantiated prompt, ready to hand to an agent. -``` - ---- - -## Usage Notes - -1. **One task per prompt.** Never batch multiple Xlib call replacements into a single prompt. The agent should make one surgical change, verify it compiles, and stop. - -2. **Always include the mapping tables.** The X11→WMEvent type and field mapping tables should be included verbatim in every Template 1 and Template 2 instantiation. The agent needs them as reference, not from memory. - -3. **Line numbers drift.** Before instantiating a template, re-grep for the exact current line number. The analysis line numbers are from the initial read and will shift as changes accumulate. - -4. **Compilation is the gate.** Every task ends with "the code compiles." If it doesn't, the task is not done. The agent should fix compilation errors before reporting completion. - -5. **STOP conditions are real.** Templates 3, 6, 7, and 8 include "STOP and report" conditions for missing vtable slots or APIs. The agent must actually stop — not invent a workaround. Missing infrastructure is a signal to create a new task, not to hack around it. - -6. **Wave ordering matters.** Wave 1 tasks are independent and can be parallelized. Wave 2 tasks depend on Wave 1. Wave 3 (WPrefs.app) depends on Wave 2. Respect this ordering when dispatching to agents. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4c0e2c1..b510090 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4,8 +4,8 @@ | Directory | Role | LOC (`.c`) | |---|---|---| -| **`src/`** | **Window manager core** — policy, layout, event dispatch | ~46k | -| **`src/backend/`** | Display-system abstraction (X11 + Wayland vtable) | ~13k | +| **`src/`** | **Window manager core** — policy, layout, event dispatch | ~26k | +| **`src/backend/`** | Display-system abstraction (X11 + Wayland vtable) | ~18k | | **`WINGs/`** | Widget library (buttons, text fields, panels, etc.) + utility library (WUtil) | ~40k | | **`wrlib/`** | **Raster graphics library** — image loading, scaling, gradients, format codecs | ~9k | | **`wmlib/`** | Tiny helper library for external apps to talk to the WM (menus, events) | ~500 | @@ -21,20 +21,19 @@ This is the window manager itself. The biggest files reveal the hotspots: | File | LOC | Responsibility | |---|---|---| -| `dock.c` | 4913 | Dock & clip (app launcher bar) | -| `defaults.c` | 3450 | Reading/writing the defaults database (user prefs) | -| `window.c` | 2899 | `WWindow` lifecycle — manage, unmanage, configure | -| `moveres.c` | 2458 | Interactive move/resize (rubber-banding, snap) | -| `menu.c` | 2407 | Menu data structure, drawing, keyboard navigation | -| `actions.c` | 2340 | High-level window actions (maximize, shade, close…) | -| `event.c` | 2076 | **Main event loop + dispatch** | -| `wmspec.c` | 2029 | EWMH / `_NET_WM_*` compliance | -| `framewin.c` | 1243 | Frame window (titlebar, buttons, resizebar) drawing | -| `icon.c` | 964 | Miniwindow / app icon rendering | -| `workspace.c` | 936 | Virtual desktop management | -| `screen.c` | 640 | Per-screen state (`WScreen`) init/teardown | -| `startup.c` | 651 | `StartUp()` — full WM initialization sequence | -| `main.c` | 792 | `main()` — arg parsing, backend selection, enters `EventLoop()` | +| `dock.c` | 4884 | Dock & clip (app launcher bar) | +| `defaults.c` | 3470 | Reading/writing the defaults database (user prefs) | +| `window.c` | 2891 | `WWindow` lifecycle — manage, unmanage, configure | +| `moveres.c` | 2448 | Interactive move/resize (rubber-banding, snap) | +| `menu.c` | 2410 | Menu data structure, drawing, keyboard navigation | +| `actions.c` | 2338 | High-level window actions (maximize, shade, close…) | +| `event.c` | 1953 | **Main event loop + dispatch** | +| `framewin.c` | 1251 | Frame window (titlebar, buttons, resizebar) drawing | +| `icon.c` | 971 | Miniwindow / app icon rendering | +| `workspace.c` | 914 | Virtual desktop management | +| `screen.c` | 641 | Per-screen state (`WScreen`) init/teardown | +| `startup.c` | 645 | `StartUp()` — full WM initialization sequence | +| `main.c` | 801 | `main()` — arg parsing, backend selection, enters `EventLoop()` | ### Key Data Structures @@ -88,7 +87,7 @@ Most handlers look up the target window via `wm_backend->context_find(win, WM_CT ## 3. Backend Abstraction — `src/backend/` -A **vtable pattern** (`WMBackend` struct, 1821-line header) with a single global pointer: +A **vtable pattern** (`WMBackend` struct, 1915-line header with ~250 function pointer slots) with a single global pointer: ```c extern const WMBackend *wm_backend; // set in main() before anything else @@ -113,11 +112,13 @@ The vtable covers **everything** the display system does: | Atoms & properties | `atom_intern`, property get/set | Implementations: -- **`backend/x11/x11_backend.c`** (4465 lines) — wraps Xlib calls -- **`backend/wayland/wl_backend.c`** (8426 lines) — wlroots compositor +- **`backend/x11/x11_backend.c`** (4998 lines) — wraps Xlib calls +- **`backend/wayland/wl_backend.c`** (10109 lines) — wlroots compositor - **`backend/x11/x11_event.c`** — X11-specific event translation - **`backend/x11/x11_props.c`** — X11 property helpers - **`backend/x11/x11_monitor.c`** — Xinerama/XRandR monitor detection +- **`backend/x11/wmspec.c`** (2015 lines) — EWMH / `_NET_WM_*` compliance +- **`backend/x11/xdnd.c`** (323 lines) — X11 drag-and-drop protocol The event type itself is backend-neutral: `WMEvent` / `WBackendEvent` defined in `WINGs/WINGs/WMEvent.h` with an enum (`WME_KEY_PRESS`, `WME_BUTTON_PRESS`, `WME_MAP_REQUEST`, etc.). diff --git a/BACKEND_ABSTRACTION.md b/BACKEND_ABSTRACTION.md index 5bdabbc..f0c89fa 100644 --- a/BACKEND_ABSTRACTION.md +++ b/BACKEND_ABSTRACTION.md @@ -1,5 +1,12 @@ # Backend-Specific Branches Remaining in `src/` Policy Code +> **Status (2026-05-26):** This document was written during the initial abstraction +> analysis. Since then, `wmspec.c` and `xdnd.c` have been **moved to +> `src/backend/x11/`** and all `_x11_raw` references have been eliminated from +> policy code (`src/*.c` now has zero `_x11_raw` accesses and zero direct Xlib +> calls). The information below remains useful as a historical reference for the +> abstraction work that was performed. + The backend vtable (`wm_backend->*()`) handles most display-system dispatch, but significant X11-specific code still lives directly in the policy files. Here's every category. --- @@ -105,7 +112,7 @@ Plus `xdnd.c` itself (312 lines) is **entirely X11-specific** — raw `XInternAt --- -## 2. Runtime `_x11_raw` Escape Hatch (16 sites) +## 2. Runtime `_x11_raw` Escape Hatch (16 sites — NOW ZERO in policy code) The `WMEvent` struct carries a `void *_x11_raw` field — a pointer to the original `XEvent`. Policy code reaches through it when the abstracted event fields aren't sufficient: @@ -213,10 +220,10 @@ xmodifier.c ░░░░░░░░░░ ██ usermenu.c ░░░░░░░░░░ ████████░░ (XSendEvent) ``` -The **three biggest remaining problems** for Wayland parity are: -1. **`wmspec.c`** — the entire EWMH implementation is raw Xlib (65 calls). On Wayland, EWMH doesn't exist, so this needs to become a no-op or be moved behind the backend. -2. **`xdnd.c`** — entirely X11. Wayland has its own DND protocol. -3. **`event.c`'s `_x11_raw` escape hatch** — 16 sites where policy code reaches through the abstraction to touch the raw `XEvent`. +**UPDATE (2026-05-26):** All three problems have been resolved: +1. **`wmspec.c`** — moved to `src/backend/x11/wmspec.c`. On Wayland, EWMH vtable slots are no-ops. +2. **`xdnd.c`** — moved to `src/backend/x11/xdnd.c`. Wayland DnD handled by wlroots. +3. **`event.c`'s `_x11_raw` escape hatch** — all 16 sites eliminated from policy code. The remaining `_x11_raw` accesses (15) are exclusively in `src/backend/` where they belong. --- diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..c129ea4 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,170 @@ +# WindowMaker Wayland Port — Roadmap + +## Current State (2026-05-26) + +The backend abstraction is complete. The `src/` policy layer has zero direct Xlib +calls and zero `_x11_raw` references. The Wayland backend (wlroots-based, 10K lines) +implements all core window management: stacking, maximize, shade, workspaces, +alt-tab, dock/clip, menus, move/resize, focus, and frame rendering. + +WPrefs.app launches on Wayland. WINGs shared widget code has zero X11 executing +on the Wayland path. The smoke test suite passes. + +What remains is **feature completion and production hardening**. + +--- + +## Priority 1: Daily-Driver Gaps + +These block using the Wayland backend as a primary desktop. + +### 1.1 Session Persistence + +`src/session.c` (614 lines) — verify save/restore works on Wayland. Window +positions are tracked in `WWindow` (backend-neutral). The only concern is +matching restored sessions to native XDG clients via `app_id` instead of +`WM_CLASS`. + +- [ ] Test: save session, restart compositor, verify window positions restored +- [ ] Add `app_id` to session file alongside `WM_CLASS` for native XDG clients +- [ ] Verify dock state persistence (icon layout in `~/GNUstep/Defaults/`) + +### 1.2 Multi-Monitor + +Currently single-output only. wlroots provides `wlr_output_layout` and +`new_output` signal. + +- [ ] Populate `WScreen.heads[]` from `wlr_output_layout_get_box` in `screen_open` +- [ ] Handle output hotplug (migrate windows on disconnect) +- [ ] Per-head usable area calculation (dock margins) +- [ ] Test with nested multi-output (`WMAKER_WAYLAND_NESTED=1` + `wlr-randr`) + +### 1.3 Clipboard + +wlroots advertises `wl_data_device_manager` and `zwp_primary_selection_v1`. +XWayland bridge auto-syncs. Needs verification. + +- [ ] Test copy/paste between native Wayland clients +- [ ] Test copy/paste between XWayland and native clients +- [ ] Test primary selection (middle-click paste) + +### 1.4 Remaining Vtable Stubs + +These vtable slots are stubs or partial implementations: + +| Slot | Purpose | Needed For | +|------|---------|------------| +| `overlay_draw_*` | Pixman animation buffer | Iconify zoom animation | +| `client_snapshot` | Window thumbnail capture | Switch panel thumbnails, icon preview | +| `window_set_border` | Scene rect border nodes | Window borders | +| `client_synth_configure` | XDG configure scheduling | Resize negotiation | +| `pointer_change_grab` | Grab parameter update | Menu tracking | + +--- + +## Priority 2: WPrefs.app Full Port + +WPrefs launches but still has 11 direct Xlib calls in 4 files. These are +small and mechanical: + +| File | Calls | What | +|------|-------|------| +| `TexturePanel.c` | 7 | Preview window (XCreateSimpleWindow, XCopyArea, XFillRectangle, XOpenDisplay) | +| `Expert.c` | 1 | XGetDefault for X11 resource query | +| `MouseSettings.c` | 1 | XChangePointerControl | +| `xmodifier.c` | 1 | XGetModifierMapping | + +The `TexturePanel.c` preview window is the only substantial item — it creates +a raw X11 window to preview textures. Replace with a WINGs pixmap widget. + +- [ ] TexturePanel.c: replace X11 preview window with WINGs `WMLabel` + pixmap +- [ ] Expert.c: guard `XGetDefault` with `if (dpy)` +- [ ] MouseSettings.c: route through backend or use libinput +- [ ] xmodifier.c: already has XKB fallback path, verify it works + +--- + +## Priority 3: Visual Completeness + +### 3.1 Iconify/Deiconify Zoom Animation + +The X11 path uses XOR GC rubber-band rectangles (`XGrabServer` + `XDrawRectangle`). +On Wayland, implement as per-frame pixman overlay buffer. + +- [ ] Implement `overlay_draw_rect` in wl_backend.c using a transient `wlr_scene_buffer` +- [ ] Wire `animateResize()` in actions.c to use the vtable + +### 3.2 Client Snapshot (Window Thumbnails) + +Used by switch panel and icon preview. X11 uses `XGetImage`. Wayland options: +- Render last committed `wlr_texture` into an RImage via pixman blit +- Use `wlr_screencopy_manager_v1` + +- [ ] Implement `client_snapshot` vtable slot using last-committed texture + +### 3.3 Window Inspector + +`src/winspector.c` — WINGs-based dialog. Should work on Wayland since WINGs +widgets are ported. Needs testing. + +- [ ] Test window inspector opens and displays attributes +- [ ] Test attribute changes apply correctly + +--- + +## Priority 4: HiDPI & Scaling + +- [ ] Set `wlr_output_set_scale` per output +- [ ] Multiply pixman buffer allocations by scale factor +- [ ] Set PangoFT2 DPI from `output.scale * 96.0` +- [ ] Ensure `WScreen.width/height` remain logical coordinates +- [ ] Test at 2x scale + +--- + +## Priority 5: Protocol Wiring + +wlroots already advertises these protocols. Wire them to policy: + +| Protocol | Purpose | Effort | +|----------|---------|--------| +| `ext-session-lock-v1` | Screen lock | Medium | +| `xdg_activation_v1` | Focus stealing prevention | Low | +| `zwp_idle_inhibit_manager_v1` | Prevent blank during video | Low | +| `ext-foreign-toplevel-list-v1` | Taskbar integration | Low | +| `zwp_input_method_v2` | IME support | Medium | +| `wlr_gamma_control_v1` | Night light | Low | + +--- + +## Priority 6: DRM/KMS Native Backend + +For running without a host compositor: + +- [ ] Use `wlr_backend_autocreate` (replaces headless/nested) +- [ ] libinput for real hardware input (wlroots handles this) +- [ ] VT switching via `wlr_session` +- [ ] Test on real hardware + +--- + +## Architecture Notes + +- **Rendering pattern**: pixman_image_t → wlr_scene_buffer → scene graph position +- **Event flow**: wlroots signals → wl_backend.c handlers → WMEvent → DispatchEvent +- **WINGs on Wayland**: compositor-internal views (not wl_surfaces), pixman-rendered, + events via ring buffer from compositor hit-testing +- **wl_backend.c (420K) should be split** into wl_frame.c, wl_menu.c, wl_input.c, + wl_toplevel.c, wl_output.c as complexity grows + +--- + +## Reference Documents + +| Document | Purpose | +|----------|---------| +| `ARCHITECTURE.md` | Codebase structure, data flow, key types | +| `BACKEND_ABSTRACTION.md` | Historical analysis of X11 leaks (all resolved) | +| `X11_IMPLEMENTATION.md` | How X11 path works (correctness reference) | +| `ACTION_PLAN_PROMPTS.md` | Task templates for mechanical porting work | +| `tools/wm-port/` | Agent-assisted task dispatch tooling | diff --git a/WAYLAND_ROADMAP.md b/WAYLAND_ROADMAP.md deleted file mode 100644 index 2acd9da..0000000 --- a/WAYLAND_ROADMAP.md +++ /dev/null @@ -1,604 +0,0 @@ -# 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 |