docs: evaluate River window manager options
This commit is contained in:
472
docs/riverwm-evaluation.md
Normal file
472
docs/riverwm-evaluation.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# River Window Manager Evaluation
|
||||
|
||||
This evaluates window managers for the current River split-compositor model,
|
||||
where River delegates policy to a client implementing
|
||||
`river-window-management-v1`.
|
||||
|
||||
It is intentionally separate from `docs/tiling-wm-experience.md`: that document
|
||||
describes the target experience, while this document evaluates current River
|
||||
implementation options against it.
|
||||
|
||||
## Short Recommendation
|
||||
|
||||
The best current candidates are:
|
||||
|
||||
1. `kwm` if the goal is the most usable latest-protocol experiment quickly.
|
||||
2. `orilla` if the goal is the closest long-term match to an XMonad-like,
|
||||
code-driven, extensible environment.
|
||||
3. `rhine` if Hyprland-like IPC and directional commands are more important
|
||||
than layout maturity.
|
||||
|
||||
No current candidate appears to satisfy the full target experience out of the
|
||||
box. The largest gaps are equal-width vertical columns, XMonad-like directional
|
||||
window control, scratchpad/minimize semantics, and taffybar-compatible state.
|
||||
|
||||
## Current River Model
|
||||
|
||||
River is now a non-monolithic Wayland compositor. It does not combine the
|
||||
compositor and window manager into one process; a separate window manager
|
||||
implements `river-window-management-v1`.
|
||||
|
||||
Older River layout generators, such as `rivertile`, `river-filtile`, and
|
||||
`wideriver`, are useful historical context but are not the primary path if the
|
||||
goal is to use the latest River architecture.
|
||||
|
||||
## Candidate Ranking
|
||||
|
||||
### 1. kwm
|
||||
|
||||
Repository: <https://github.com/kewuaa/kwm>
|
||||
|
||||
Fit: best practical starting point.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Zig.
|
||||
- Most popular candidate found.
|
||||
- Very active as of late April 2026.
|
||||
- Implements `river-window-management-v1`.
|
||||
- Dynamic tiling with `tile`, `grid`, `monocle`, `deck`, `scroller`, and
|
||||
floating layouts.
|
||||
- Has tags, shift-tags, regex window rules, modes, runtime configuration reload,
|
||||
and several window states.
|
||||
- Uses a runtime `config.zon` file with partial overrides and live reload; it is
|
||||
DWM-like, but ordinary configuration is not patch-based.
|
||||
|
||||
Risks:
|
||||
|
||||
- It is DWM-like, not XMonad-like.
|
||||
- Default bindings are mostly next/previous rather than geometric WASD
|
||||
directional focus and movement.
|
||||
- The closest built-in layout to equal columns is probably `grid`, but this
|
||||
needs empirical testing.
|
||||
- It has its own status bar model; taffybar integration is not solved.
|
||||
|
||||
Use this first if the goal is to get a working River session and discover
|
||||
practical gaps quickly.
|
||||
|
||||
### 2. orilla
|
||||
|
||||
Repository: <https://git.sr.ht/~hokiegeek/orilla>
|
||||
|
||||
Fit: best philosophical match.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Rust.
|
||||
- Explicitly inspired by XMonad.
|
||||
- Code-first configuration model.
|
||||
- Dynamic tiling with composable layouts.
|
||||
- Has tag actions for switching, shifting, previous tag, next/previous tag,
|
||||
next empty tag, and shift-to-empty tag.
|
||||
- Has a fullscreen/monocle-like layout.
|
||||
|
||||
Risks:
|
||||
|
||||
- Early project.
|
||||
- Low adoption compared with `kwm`.
|
||||
- Built-in layouts do not appear to include the exact equal-width vertical
|
||||
columns layout.
|
||||
- Current exposed actions are ordered focus/swap actions, not geometric
|
||||
directional navigation.
|
||||
- The implementation stores window rectangles, so directional navigation looks
|
||||
patchable, but it is not available today.
|
||||
- Current layout application appears early in its multi-output support; layout
|
||||
calculation uses the first output rather than a mature per-output model.
|
||||
- Output movement, scratchpads, minimization, and taffybar state would likely
|
||||
require extension work.
|
||||
|
||||
Use this first if the goal is to build toward the exact target experience rather
|
||||
than maximize out-of-the-box coverage.
|
||||
|
||||
### 3. rhine
|
||||
|
||||
Repository: <https://codeberg.org/sivecano/rhine>
|
||||
|
||||
Fit: interesting integration candidate.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Zig.
|
||||
- Active as of late April 2026.
|
||||
- Modular design.
|
||||
- Has directional commands such as `moveFocus <dir>` and `moveWindow <dir>`.
|
||||
- Has workspace commands including send-to-workspace and move-to-workspace.
|
||||
- Provides partial Hyprland IPC compatibility, which may be useful for tooling
|
||||
and taffybar experiments.
|
||||
|
||||
Risks:
|
||||
|
||||
- Only a BSP-style layout appears to be implemented currently.
|
||||
- Special workspaces and some monitor/workspace behavior are still TODO-level.
|
||||
- Less popular than `kwm`.
|
||||
- Equal vertical columns and tabbed/monocle behavior are not obvious fits.
|
||||
|
||||
Use this for a small spike if the Hyprland IPC bridge looks like the shortest
|
||||
path to taffybar state.
|
||||
|
||||
### 4. beansprout
|
||||
|
||||
Repository: <https://codeberg.org/beansprout/beansprout>
|
||||
|
||||
Fit: viable but less aligned.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Zig.
|
||||
- Active as of late April 2026.
|
||||
- DWM-style dynamic tiling.
|
||||
- KDL configuration.
|
||||
|
||||
Risks:
|
||||
|
||||
- Primary/stack model conflicts with equal-width columns.
|
||||
- Per-output tags conflict with the target shared global workspace model.
|
||||
- Less popular than `kwm`.
|
||||
|
||||
### 5. rill
|
||||
|
||||
Repository: <https://codeberg.org/lzj15/rill>
|
||||
|
||||
Fit: low.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Zig.
|
||||
- Active.
|
||||
- More popular than several smaller candidates.
|
||||
|
||||
Risks:
|
||||
|
||||
- It is a scrolling window manager, which does not match the target layout
|
||||
model.
|
||||
|
||||
### 6. notion-river
|
||||
|
||||
Repository: <https://github.com/Marenz/notion-river>
|
||||
|
||||
Fit: low despite good tab support.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Rust.
|
||||
- Has persistent frames and tabbed windows.
|
||||
- Has IPC and cross-monitor focus/move behavior.
|
||||
|
||||
Risks:
|
||||
|
||||
- It is explicitly static tiling, not dynamic tiling.
|
||||
- Persistent frames conflict with the target equal dynamic columns behavior.
|
||||
- Workspaces are assigned per output.
|
||||
|
||||
### 7. rivulet
|
||||
|
||||
Repository: <https://github.com/jackiedorland/rivulet>
|
||||
|
||||
Fit: watchlist only.
|
||||
|
||||
Reasons to consider it:
|
||||
|
||||
- Haskell.
|
||||
- Configured in a Haskell DSL.
|
||||
- Conceptually closest to the desire for an XMonad-like programmable WM.
|
||||
|
||||
Risks:
|
||||
|
||||
- The README currently says not to use it and that it is not usable as a window
|
||||
manager yet.
|
||||
- Many core behaviors are still unfinished.
|
||||
|
||||
### Historical Option: wideriver
|
||||
|
||||
Repository: <https://github.com/alex-courtis/wideriver>
|
||||
|
||||
Fit: not a latest-protocol candidate.
|
||||
|
||||
Reasons it is still worth knowing about:
|
||||
|
||||
- Inspired by DWM and XMonad.
|
||||
- Has layouts closer to classic dynamic tiling expectations.
|
||||
- More established than several current-protocol candidates.
|
||||
|
||||
Reason not to choose it now:
|
||||
|
||||
- It belongs to the older layout-generator model, not the latest River
|
||||
`river-window-management-v1` window-manager model.
|
||||
|
||||
## Requirement Fit
|
||||
|
||||
### Dynamic Tiling
|
||||
|
||||
Best candidates: `kwm`, `orilla`, `beansprout`.
|
||||
|
||||
`kwm` has the broadest ready-made layout set. `orilla` is the better extension
|
||||
target if writing a custom layout is acceptable.
|
||||
|
||||
### Equal Vertical Columns
|
||||
|
||||
No candidate clearly satisfies this out of the box.
|
||||
|
||||
Likely approaches:
|
||||
|
||||
- Test `kwm` grid layout first.
|
||||
- Implement an `EqualColumns` layout in `orilla`.
|
||||
- Avoid primary/stack defaults unless they can be configured into equal columns.
|
||||
|
||||
### Monocle or Fullscreen-Like Layout
|
||||
|
||||
Best candidates: `kwm`, `orilla`, `notion-river`.
|
||||
|
||||
`kwm` has monocle. `orilla` has a fullscreen/monocle-like layout. `notion-river`
|
||||
has strong tabbed behavior but fails the dynamic-layout requirement.
|
||||
|
||||
### Directional Navigation and Window Movement
|
||||
|
||||
Best candidate: `rhine`.
|
||||
|
||||
`rhine` exposes directional focus and movement commands. `kwm` and `orilla`
|
||||
appear to need either custom action work or careful testing to determine whether
|
||||
geometric WASD behavior can be made exact.
|
||||
|
||||
### Workspace Movement
|
||||
|
||||
Best candidates: `orilla`, `kwm`, `rhine`.
|
||||
|
||||
`orilla` has explicit tag actions for last tag, next/previous tag, next empty
|
||||
tag, and shift-to-empty. `kwm` has tags and shift-tags. `rhine` has explicit
|
||||
send-to-workspace and move-to-workspace commands.
|
||||
|
||||
### Scratchpads and Minimization
|
||||
|
||||
No candidate clearly solves this in the target form.
|
||||
|
||||
Likely implementation requires reserved hidden/special state in the window
|
||||
manager, not just shell scripts. River's protocol has hide/show primitives, so a
|
||||
native WM implementation has enough substrate, but each candidate needs review or
|
||||
extension.
|
||||
|
||||
### Overview and Discovery
|
||||
|
||||
No candidate clearly provides a Hyprexpo-like overview.
|
||||
|
||||
Practical first implementation is probably a rofi/launcher workflow built from
|
||||
window state. `lswt` may be useful as a compositor-neutral Wayland toplevel
|
||||
source, but accurate workspace positioning is likely WM-specific.
|
||||
|
||||
### Taffybar State
|
||||
|
||||
Best candidate to investigate: `rhine`.
|
||||
|
||||
The partial Hyprland IPC compatibility is the only candidate feature that looks
|
||||
directly relevant to existing Hyprland-oriented tooling. Otherwise, taffybar
|
||||
will need a River/current-WM backend or a normalization daemon.
|
||||
|
||||
Required state includes:
|
||||
|
||||
- workspaces/tags
|
||||
- focused output
|
||||
- active workspace per output
|
||||
- workspace history per output
|
||||
- windows per workspace
|
||||
- app-id/class, title, active state, urgency if available
|
||||
- minimized and scratchpad filtering
|
||||
- approximate window position
|
||||
|
||||
## Proposed Prototype Plan
|
||||
|
||||
1. Package or run `kwm` and `orilla` locally without making either a default
|
||||
login session.
|
||||
2. Start with `kwm` to measure out-of-the-box usability: tags, previous tag,
|
||||
grid, monocle, send-to-tag, shift-to-empty, and output movement.
|
||||
3. In parallel or immediately after, sketch an `orilla` configuration with an
|
||||
`EqualColumns` layout and the workspace actions needed by the spec.
|
||||
4. Do a small `rhine` spike only for Hyprland IPC and taffybar feasibility.
|
||||
5. Choose between `kwm` and `orilla` after testing layout fidelity and state
|
||||
export.
|
||||
|
||||
Expected decision:
|
||||
|
||||
- Choose `kwm` if it gets close enough with configuration and light scripting.
|
||||
- Choose `orilla` if exact XMonad-like behavior matters enough to write Rust.
|
||||
- Do not choose `rivulet` until it becomes usable.
|
||||
- Do not choose `notion-river` unless the dynamic-layout requirement changes.
|
||||
|
||||
## Orilla Deep Dive
|
||||
|
||||
Commit checked: `bd77afb`, "adds support for finding next empty tag".
|
||||
|
||||
### What Orilla Already Has
|
||||
|
||||
- Rust code-first configuration.
|
||||
- A small action model for keybindings.
|
||||
- Dynamic layout abstraction through the `Layout` trait.
|
||||
- Built-in `Tall` and `Full` layouts.
|
||||
- Per-tag layout selection hooks.
|
||||
- Tags with switch, toggle, shift, last, next, previous, next empty, and
|
||||
shift-to-empty actions.
|
||||
- Window tracking for internal IDs, app-id, title, geometry, focus, tags,
|
||||
hidden state, and closed state.
|
||||
- River hide/show calls for non-visible windows.
|
||||
- A simple JSON IPC stream over a Unix socket containing tags, focused tag,
|
||||
windows, app-id/title, focused window state, and current layout.
|
||||
- Passing test suite at the checked commit.
|
||||
|
||||
### Directional Navigation
|
||||
|
||||
Orilla does not currently provide geometric directional navigation.
|
||||
|
||||
Existing window actions are:
|
||||
|
||||
- close focused window
|
||||
- focus primary
|
||||
- focus next
|
||||
- focus previous
|
||||
- promote focused window
|
||||
- swap next
|
||||
- swap previous
|
||||
|
||||
This means `Super+WASD` cannot be mapped directly to "focus the closest window
|
||||
in that direction" today.
|
||||
|
||||
The good news is that Orilla stores enough geometry to implement it. Each window
|
||||
has `x`, `y`, `width`, and `height`, and layout application updates those fields.
|
||||
A plausible patch would add:
|
||||
|
||||
- `Direction::{Up, Down, Left, Right}`
|
||||
- `WindowAction::FocusDirection(Direction)`
|
||||
- `WindowAction::SwapDirection(Direction)`
|
||||
- geometry helpers on `WindowState`
|
||||
- keybinding constructors such as `action::focus_direction(Direction::Left)`
|
||||
|
||||
The selection heuristic should match XMonad-style behavior: compare candidate
|
||||
window rectangles against the focused window rectangle, filter to the requested
|
||||
direction, prefer overlapping windows on the perpendicular axis, then minimize
|
||||
distance.
|
||||
|
||||
### Equal Columns Layout
|
||||
|
||||
Orilla does not currently include the desired equal-width vertical columns
|
||||
layout.
|
||||
|
||||
This is straightforward to implement as a new `Layout`: divide the output width
|
||||
by the number of visible windows, give each visible window full output height,
|
||||
and distribute remainder pixels across the first columns.
|
||||
|
||||
This is likely easier in Orilla than in most alternatives because layouts are
|
||||
plain Rust implementations of a small trait.
|
||||
|
||||
### Fullscreen and Monocle
|
||||
|
||||
Orilla has a `Full` layout. It places every visible window at the full output
|
||||
rectangle and sorts the focused window last so it renders on top.
|
||||
|
||||
This covers monocle-style behavior, but it is not tabbed. Tab UI would need a
|
||||
separate layer-shell surface, IPC consumer, or native extension.
|
||||
|
||||
Directional focus could still work in `Full` if implemented against window order
|
||||
as a fallback when all rectangles overlap.
|
||||
|
||||
### Workspaces and Tags
|
||||
|
||||
Orilla's tag model is useful for the target workspace behavior:
|
||||
|
||||
- switch to specific tag
|
||||
- move focused window to specific tag
|
||||
- toggle visible tag
|
||||
- last tagset
|
||||
- next/previous tag
|
||||
- next empty tag
|
||||
- move focused window to next empty tag
|
||||
|
||||
Gaps:
|
||||
|
||||
- "move window to workspace and follow" is not a first-class action, but can be
|
||||
added by combining shift and switch semantics.
|
||||
- Last workspace is global to the tag manager, not per monitor.
|
||||
- There is no monitor-specific workspace history yet.
|
||||
|
||||
### Monitors and Outputs
|
||||
|
||||
This is the largest architectural gap after directional navigation.
|
||||
|
||||
Orilla tracks River outputs, but windows do not currently carry an output
|
||||
assignment. Layout application uses the first output from the output map and
|
||||
lays out the global visible window set there. That is enough for early/single
|
||||
monitor use, but not enough for the target multi-monitor experience.
|
||||
|
||||
To meet the spec, Orilla likely needs:
|
||||
|
||||
- active output tracking
|
||||
- window-to-output assignment
|
||||
- per-output visible tag/history model, or a shared tag model with explicit
|
||||
output occupancy
|
||||
- layout calculation per output
|
||||
- focus-output and send-window-to-output actions
|
||||
- useful focus behavior after moving a window between outputs
|
||||
|
||||
### Scratchpads and Minimization
|
||||
|
||||
Orilla has the basic substrate but not the feature.
|
||||
|
||||
The substrate:
|
||||
|
||||
- windows have tags
|
||||
- windows have hidden state
|
||||
- the WM can call River hide/show
|
||||
- app-id/title are tracked
|
||||
|
||||
Missing:
|
||||
|
||||
- named scratchpad rules
|
||||
- delayed title/app-id matching policy
|
||||
- restore behavior
|
||||
- minimized-window list/order
|
||||
- keybinding actions for hide/restore
|
||||
- IPC fields distinguishing normal, scratchpad, and minimized windows
|
||||
|
||||
### Taffybar State
|
||||
|
||||
Orilla's existing IPC is promising but too small.
|
||||
|
||||
Current IPC exports tags, focused tag, current layout, and each tag's windows
|
||||
with app-id/title/focused. It does not yet export output identity, active output,
|
||||
window geometry, hidden/minimized/scratchpad classification, urgency, or
|
||||
per-monitor history.
|
||||
|
||||
This is still a better starting point than having no state export, because it is
|
||||
already JSON over a Unix socket and sits inside the WM where the necessary state
|
||||
should live.
|
||||
|
||||
### Bottom Line
|
||||
|
||||
Orilla has the right shape if we are willing to write WM code. It does not have
|
||||
the exact daily-driver behavior today.
|
||||
|
||||
The likely minimum patch set before serious use is:
|
||||
|
||||
1. Equal-columns layout.
|
||||
2. Geometric directional focus and swap.
|
||||
3. Move-focused-window-to-tag-and-follow.
|
||||
4. Multi-output model.
|
||||
5. IPC expansion for taffybar and window menus.
|
||||
6. Scratchpad/minimize state.
|
||||
Reference in New Issue
Block a user