# 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