50 KiB
WindowMaker Wayland Port — Roadmap
Last updated: 2026-05-18
Governing Principles
-
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 WAYLANDor runtime backend checks in shared code. -
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.
-
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.
-
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.cbackends - wrlib/ — Raster graphics library (image loading, manipulation, rendering)
Completed (as of 2026-05-18)
- Backend vtable abstraction (150+ slots)
- Policy layer fully X11-free (zero direct Xlib calls in WM core)
- wlroots compositor (headless + nested modes)
- XDG shell + XWayland bridge
- Frame rendering (titlebar, buttons, resizebar) via pixman → wlr_scene_buffer
- Menu rendering + highlighting + item activation
- Root menu from config file
- Dock rendering (SHM panel, icon slots, app tracking)
- Clip rendering
- Background rendering
- Mouse input (motion, buttons, scroll, grabs)
- Keyboard input (XKB, modifiers, keysym translation)
- Interactive move/resize with edge detection + cursor changes
- Click-to-focus via hit-testing
- Run dialog (WINGs modal loop with compositor tick)
- Window manage/unmanage lifecycle (XDG toplevel + XWayland)
- Focus delivery (wlr_seat_keyboard_notify_enter)
- WINGs Wayland backend (views, fonts/PangoFT2, events/ring buffer, rendering/pixman, colors)
- Smoke test suite (30+ tests in tests/wl-smoke-test.sh)
- Alt-Tab window cycling (switch panel)
- Wave 1 complete: all XEvent* → WMEvent* signature changes (8 tasks: A5.1, A5.2, B.3, E.3, E.4, R.1, R.2, R.3)
- 7 _x11_raw references eliminated in policy code (
src/: 16 → 9 remaining) - Crash regression fixed: use-after-free on modal dialog close —
W_IsValidView()validates stale_view_targetagainstview_tableat fetch boundary inwevent_wl.c; defense-in-depth inwview.candwevent.c - Scene graph cleanup: WINGs toplevel close destroys
wlr_scene_nodeand drops buffer viaW_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.cis inside#if 0— dead code - Native XDG client rendering fix:
surface_commit_listenercallswlr_output_schedule_frame()on every buffer commit (commit8d963e7)
Phase A: Core Window Management Gaps
Priority: HIGH — needed for daily-driver use.
| # | Task | Key Files | Complexity |
|---|---|---|---|
| A1 | stacking.c, wlr_scene_node_raise/lower | ✅ Done | |
| A2 | actions.c, usable area calc | ✅ Done | |
| A3 | actions.c, frame buffer resize | ✅ Done | |
| A4 | Workspace switching (create/destroy/switch) | workspace.c, scene tree reparenting | Medium |
| A5 | cycling.c, switchpanel.c, vtable switchpanel_enter | Medium | |
| A6 | icon.c, appicon.c, vtable icon_paint/icon_update_pixmap | Done | |
| A7 | Window placement algorithms (smart/cascade/random) | placement.c (may already work via vtable) | Low |
Phase B: Dock & App Icons
| # | Task | Key Files | Complexity |
|---|---|---|---|
| B1 | App icon rendering (load .tiff/.png, render into dock) | appicon.c, wrlib loaders, vtable appicon_show | Medium |
| B2 | Dock icon drag & drop (reorder, add/remove) | dock.c DnD handlers | Medium |
| B3 | Dock auto-launch on login | dock.c session restore | Low |
| B4 | Clip workspace indicator (number, arrows) | dock.c clip paint | Low |
| B5 | Drawer support (horizontal dock extension) | dock.c drawer subsystem | Medium |
| B6 | Icon bounce on launch | vtable appicon_bounce | Low |
Phase C: Visual Polish & Animations
| # | Task | Key Files | Complexity |
|---|---|---|---|
| C1 | Slide animations (iconify/deiconify zoom) | vtable slide_window | Medium |
| C2 | Menu scrolling (long menus) | menu.c scroll logic | Low |
| C3 | Balloon tooltips (dock icon hover) | vtable balloon_show | Low |
| C4 | Cursor themes (xcursor loading) | wlr_xcursor_manager | Low |
| C5 | Window opacity (NETWM alpha) | vtable netwm_set_opacity | Low |
Phase D: WINGs Widgets (for Preferences & Inspectors)
Core rendering backends exist. Complex widgets need verification/porting:
| Widget | Needed For | Notes |
|---|---|---|
| WMList | File panels, icon chooser | Test |
| WMBrowser | File browser in dialogs | Test |
| WMColorPanel | Color picker in prefs | May need porting |
| WMFontPanel | Font selection | May need porting |
| WMFilePanel | Open/save dialogs | Test |
| WMTabView | Preferences app tabs | Test |
| WMScroller/WMScrollView | Scrollable content | Test |
| WMSlider | Preference sliders | Test |
| WMPopUpButton | Dropdown menus in prefs | Test |
| WMTextField (advanced) | Multi-line editing | Test |
| Drag & Drop | Icon DnD, file DnD | Likely needs porting |
Phase E: Dialogs & Panels
| # | Task | Key Files | Complexity |
|---|---|---|---|
| E1 | Window Inspector (per-window attributes) | winspector.c | High (many widgets) |
| E2 | Info Panel (about dialog) | dialog.c | Low |
| E3 | Crash Dialog (recovery UI) | dialog.c | Low |
| E4 | Icon Chooser (browse icon files) | dialog.c | Medium |
| E5 | Preferences App (full GUI) | WPrefs.app/ | High |
Phase F: wrlib Wayland Integration
| # | Task | Description | Complexity | Status |
|---|---|---|---|---|
| F1 | pixman-backed RContext without X11 Display | Medium | ✅ Done | |
| F2 | Direct path without XImage | Low (may exist) | ✅ Done | |
| F3 | Solid, gradient, pixmap textures | Verify | ✅ Done | |
| F4 | PNG/JPEG/GIF/WebP (X11-independent) | Verify | ✅ Done |
Phase F: COMPLETE — all wrlib Wayland paths verified, zero code changes needed.
Phase G: Session & Persistence
| # | Task | Key Files | Complexity |
|---|---|---|---|
| G1 | Session save/restore (positions, workspaces) | session.c | Medium |
| G2 | Dock state persistence (icon layout) | dock.c state files | Low (may work) |
| G3 | Defaults database (~/GNUstep/Defaults/) | defaults.c | Verify |
| G4 | Autostart (launch apps from session) | startup.c | Low |
Phase H: Advanced Compositor Features
| # | Task | Description | Complexity |
|---|---|---|---|
| H1 | Multi-monitor (output layout, per-output workspaces) | wlr_output_layout, screen.c | High |
| H2 | HiDPI / output scaling | wlr_output_set_scale, buffer scaling | Medium |
| H3 | Gamma control (night light) | Already advertised, wire up | Low |
| H4 | Screen lock integration | ext-session-lock-v1 protocol | Medium |
| H5 | Clipboard (copy/paste between clients) | wl_data_device (may work via wlroots) | Verify |
| H6 | Primary selection | Already advertised | Verify |
| H7 | Screencopy (screenshots) | Already advertised | Verify |
| H8 | DMA-BUF (GPU buffer sharing) | Already advertised | Verify |
| H9 | Input method (IME support) | Already advertised | Medium |
Phase I: EWMH / Foreign Toplevel
| # | Task | Description | Complexity |
|---|---|---|---|
| I1 | Foreign toplevel list (taskbar integration) | Already advertised, verify events | Low |
| I2 | XDG activation (focus stealing prevention) | Wire to policy | Low |
| I3 | Idle inhibit (prevent blank during video) | Already advertised | Low |
Phase J: DRM/KMS Backend (Native)
For running without a host compositor:
| # | Task | Description | Complexity |
|---|---|---|---|
| J1 | DRM backend | wlr_backend_autocreate | Medium |
| J2 | libinput (real hardware input) | wlroots handles this | Low |
| J3 | VT switching | wlr_session | Low |
| J4 | Multi-GPU | Secondary GPU rendering | High |
Suggested Implementation Order
NOW (daily-driver basics):
A1. Stacking (raise/lower)
A2. ~~Maximize~~ ✅
A3. ~~Shade~~ ✅
A4. Workspace switching
~~A5. Alt-Tab cycling~~ ✅
A6. Icon/minimize
A7. Placement (verify)
NEXT (visual completeness):
B1. App icon rendering (wrlib → pixman)
F1. wrlib RContext for Wayland
C1. Slide animations
C3. Balloon tooltips
THEN (full feature parity):
D*. Test all WINGs widgets
E1. Window Inspector
E4. Icon Chooser
G*. Session persistence
B2-B6. Full dock features
LATER (production-ready):
H1. Multi-monitor
H2. HiDPI
J*. DRM/KMS native backend
E5. WPrefs app
Technical Notes
- Rendering pattern: pixman_image_t → wlr_scene_buffer → position in scene graph
- wrlib is mostly X11-independent — only context.c and convert.c touch X11
- WINGs complex widgets are the hardest remaining work (subtle X11 assumptions in events/coords)
- wlroots handles compositor plumbing — protocols advertised, work is wiring to policy
- 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
- 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 |