# ADR-009: Pluggable Window Management Protocol ## Status Accepted ## Context One of SunRay's strengths in the Unix community was running on Solaris with its rich X11 ecosystem -- users could choose any window manager (CDE, FVWM, dwm, i3, etc.) because X11 cleanly separated the display server from window management via ICCCM/EWMH. Wayland merged the compositor and window manager into one process, killing this flexibility. River (a Zig/wlroots compositor) pioneered re-separating them via a custom Wayland protocol (`river-window-management-v1`). WayRay should adopt this pattern to let users choose their workflow: floating, tiling, dynamic, or anything custom. ## Design ### Architecture ``` ┌─────────────────────────┐ │ WayRay Compositor │ │ │ │ Wayland protocol impl │ │ Rendering + encoding │ │ Input dispatch │ │ Session management │ │ │ │ ┌────────────────────┐ │ │ │ WM Protocol Server │ │ <── Custom Wayland protocol │ └────────┬───────────┘ │ └───────────┼──────────────┘ │ Unix socket (Wayland protocol) │ ┌───────────┴──────────────┐ │ Window Manager Process │ │ │ │ Layout algorithm │ │ Focus policy │ │ Keybinding handling │ │ Decoration decisions │ │ Workspace management │ │ │ │ (any language with │ │ Wayland client bindings) │ └───────────────────────────┘ ``` The WM is a regular Wayland client that connects to the compositor and binds the `wayray_window_manager_v1` global. Only one WM can be bound at a time. ### Two-Phase Transaction Model (from River) Inspired by River's `river-window-management-v1`, all WM operations happen in two atomic phases: **Phase 1: Manage (Policy Decisions)** 1. Compositor sends `manage_start` when state changes occur (new window, close, resize request, fullscreen request) 2. WM evaluates and responds with policy: `propose_dimensions`, `set_focus`, `use_ssd`/`use_csd`, `grant_fullscreen`/`deny_fullscreen` 3. WM calls `manage_done` 4. Compositor sends `xdg_toplevel.configure` to affected windows **Phase 2: Render (Visual Placement)** 1. After clients acknowledge configures and commit, compositor sends `render_start` 2. WM specifies visual state: `set_position`, `set_z_order`, `set_borders`, `show`/`hide` 3. WM calls `render_done` 4. Compositor applies all changes atomically in one frame ### Protocol Interfaces #### `wayray_wm_manager_v1` (Global) ```xml ``` #### `wayray_wm_window_v1` (Per-Window) ```xml ``` #### `wayray_wm_seat_v1` (Input/Keybindings) ```xml ``` #### `wayray_wm_workspace_v1` (Virtual Desktops / Tags) ```xml ``` ### Supported WM Paradigms | Paradigm | Example | How It Maps | |----------|---------|-------------| | Floating | Openbox, FVWM | Arbitrary `set_position` + `set_z_*` + interactive move/resize | | Tiling | i3, dwm | Calculate layout on `manage_start`, batch `propose_dimensions` + `set_position` | | Dynamic | awesome, xmonad | Switch layout algorithm, re-layout all windows atomically | | Stacking/keyboard | ratpoison, StumpWM | Fullscreen windows, `hide`/`show` + binding modes for prefix keys | | Scrolling | niri, PaperWM | Positions outside visible area, `set_position` with smooth offsets | | Tags | dwm | `set_window_tags` bitmask, windows visible on multiple tags simultaneously | ### Default WM WayRay ships with a built-in **floating WM** as the default. If no external WM connects, the built-in WM handles window placement with sane defaults (centered new windows, basic keyboard shortcuts). When an external WM connects, the built-in WM yields. ### Hot-Swap and Crash Resilience - If the WM process crashes, the compositor continues running. Windows freeze in their last positions. A new WM can connect and take over. - Hot-swap: a new WM can connect while the old one is running. The compositor sends a `replaced` event to the old WM, which should disconnect gracefully. - On WM connect, the compositor sends the full window list so the WM can reconstruct state. ## Options Considered ### 1. Monolithic (WM logic in compositor) - Simplest implementation - Cannot swap WMs without restarting compositor (and all sessions!) - Violates Unix philosophy - Rejected: kills the "choose your workflow" feature ### 2. In-process plugin (like Wayfire/Compiz) - WM loaded as a shared library - Fast (no IPC overhead) - Crash in WM crashes the compositor - Language-restricted (must be Rust or C FFI) - Rejected: not crash-resilient, not language-agnostic ### 3. External process via custom Wayland protocol (River model) - Clean separation, crash-resilient, hot-swappable - Language-agnostic (any Wayland client library) - Small IPC overhead (negligible: manage/render happen once per frame at most) - More complex to implement - **Selected**: best fit for WayRay's values ## Rationale - Honors the X11 tradition of WM choice that Unix enthusiasts loved - A SunRay replacement that only offers one workflow would alienate the community - River proved this works in practice with their two-phase model - Crash resilience is critical for a thin client server (WM crash must not kill user sessions) - Enables a community ecosystem of WMs in any language - The two-phase transaction model prevents visual glitches (no partial layouts visible) ## Consequences - Must implement and maintain a custom Wayland protocol - Default floating WM adds code but is necessary for out-of-box usability - WM developers need documentation and example implementations - Slightly more latency than monolithic (one extra IPC roundtrip per layout change, ~microseconds) - The protocol must be versioned carefully; breaking changes affect all WM implementations