Maverick is a tiling window manager for the X Window System, written in Rust. It combines a scrollable, column-based tiling layout with a built-in OpenGL/GLX compositor, and is designed to keep its logical window state and the real X11 state in agreement through an explicit, reconciled state pipeline.
Note: Maverick targets X11 only. There is no Wayland backend.
Maverick manages windows on an X11 display. Its core is a pure-Rust state model that decides where every window should be; a backend layer translates those decisions into X11 protocol calls and into compositor draw commands. The window manager and its compositor share a single X11 connection.
Design priorities:
- Minimal external dependencies. The window manager core uses
x11rb(pure Rust) and links no windowing toolkit. Configuration parsing, PNG decoding, and the TOML parser are implemented in-tree with no third-party crates. GL/GLX access is provided by a hand-written FFI layer. - A single dispatch path from input and IPC to state change
(
Engine::dispatch(Action) -> Vec<Effect>), so behaviour is identical whether a change originates from a keypress or a control command. - Explicit ownership of applied geometry, with invariants checked at runtime, to avoid divergence between the logical layout and the actual X11 window positions.
- A built-in compositor, so no external compositor is required for translucency, rounded corners, or a drawn wallpaper.
- Tiling of managed windows across a scrollable column layout and a grid layout.
- Floating windows, clamped to the monitor workarea, with drag-to-move and drag-to-resize via the pointer.
- Fullscreen and Maximize implemented as distinct presentation modes:
- Maximize fills the workarea (screen minus reserved regions), keeps a zero border, is per-axis (horizontal and vertical independently), and is applied only while the window is focused. On focus loss it returns to its tiling slot.
- Fullscreen covers the whole screen (or, under the Column layout's "Normal" policy, participates in the scrolling ribbon), independent of focus.
- Focus model with directional navigation, optional focus-follows-mouse
(
focus_mouse), and optional cursor warping (warp_cursor). - Workspaces are tracked per monitor (each monitor has its own active workspace), numbered and optionally named. Default count is 9.
- Multi-monitor support through RandR. The workarea of each monitor accounts for reserved space published by docks and bars via EWMH struts.
- Window rules matched by
WM_CLASS(class/instance), window type, and title (case-insensitive substring), supporting floating, sticky, workspace assignment, opacity, border, and fullscreen-policy overrides. - EWMH/ICCCM compliance, including
_NET_WM_STATE,_NET_WM_DESKTOP,_NET_ACTIVE_WINDOW, and window-type handling. override_redirectisolation: external bars, menus, and overlays are not managed as tiled windows, and theirConfigureRequests pass through directly.- Client lifecycle: managed windows are discovered, tracked, and cleaned up on unmap/destroy; a self-unmap guard prevents the WM from unmanaging its own redirected surfaces.
- Resize semantics: tiled-window geometry is owned by the WM. A tiled
window's own
ConfigureRequestis ignored (the WM echoes its own rect back), while floating windows honorConfigureRequestand are clamped to the workarea. Tiled resizing is done by changing the focused column's width (GrowCol/ShrinkCol).
Maverick ships two layout modes, switchable at runtime and tracked per workspace.
- Column (default): a horizontally scrollable ribbon of columns. Each column has a
weightexpressed as a fraction of the workarea width; adding, growing, or removing a column does not resize its neighbours. A spring-driven camera scrolls the ribbon to keep the focused column centred. An optional accordion factor can expand the focused column. Rows within a column are uniform height and do not reflow on focus change. - Grid: a deterministic engine that partitions the window set into balanced rows (longer rows on top) and assigns windows stably across inserts and removals.
Cycle between modes with the layout-cycle binding; set a specific mode with the column/grid bindings. Floating windows are preserved and clamped in both layouts.
Maverick brings up its own OpenGL/GLX compositor when the display supports it.
The compositor is enabled by default and falls back automatically to the plain
X11 (ConfigureWindow) path when OpenGL is unavailable, another compositor
already owns the screen, or context creation fails. It can also be disabled
explicitly (see Configuration).
Architecturally, the compositor:
- Uses XComposite manual redirection (
RedirectManual) on the root's subwindows and draws into the composite overlay window (CompositeGetOverlayWindow), whose input region is emptied so input passes through. - Claims the compositor selection (
_NET_WM_CM_S0); it refuses to start if another compositor already owns it. - Creates a GLX context at OpenGL 3.3 core profile and requires
GLX_EXT_texture_from_pixmap,GLX_ARB_create_context, andGLX_ARB_create_context_profile. - Imports window contents via texture-from-pixmap (
glXBindTexImageEXT), which is a driver-side rebind with no CPU copy of pixel data; there is no readback/copy fallback path. - Tracks damage via XDamage (
ReportLevel::NON_EMPTY) and accumulates aDirtyReasonbitmask (damage, geometry, surface, focus, wallpaper) describing why a frame is dirty. - Maintains a scene of one texture per client plus a z-order stack, rebuilt from the X window stack on restack/configure.
- Performs occlusion-aware damage: windows fully covered by a single opaque, square-cornered, on-screen window above them are skipped in the draw pass.
- Performs viewport culling: off-screen windows (with a small margin) are skipped.
- Performs partial redraw only when possible: a partial redraw limited to the
accumulated damage region is used when the
GLX_EXT_buffer_ageextension is available and the back buffer still holds the previously presented frame (observed age 1). Otherwise the compositor performs a full redraw. Do not assume partial redraw is always available. - Schedules frames with a frame scheduler that decides whether a frame is
needed and when, and presents with vsync through swap-interval 1
(
GLX_EXT_swap_control/GLX_MESA_swap_control/GLX_SGI_swap_control).GLX_SGI_video_syncis used only for instrumentation, not for pacing.
Effects currently implemented are per-window opacity (via
_NET_WM_WINDOW_OPACITY), rounded-corner SDF rendering, and the drawn
wallpaper. There is no blur or shadow effect.
Presentation is driven by a spring-based camera (configurable stiffness and
damping). The same spring model drives the column scroll, the Overview
film-strip zoom, the viewport zoom, and page-snap scrolling. Animation is
advanced by the frame delta and substepped internally so the integrator
stays stable; the compositor redraws at the current spring value each frame.
Presentation timing is governed by glXSwapBuffers with swap interval 1.
Maverick does not claim independence from the display refresh rate beyond
vsync-aligned presentation.
The wallpaper is drawn by the compositor and can be:
- a still image (PNG decoded natively by
maverick-img, with other formats delegated to an external converter), or - a live GLSL fragment shader receiving
u_time,u_resolution, andu_delta_time.
A Video wallpaper source is reserved but not implemented (it is ignored
with a warning).
Maverick exposes a control plane over a Unix socket. Two thin clients share one engine:
maverickctl— the general-purpose administration and query tool.maverick-msg— a verbatim command forwarder: any line it receives is forwarded to the control socket'sdispatchaction.
Both talk to a per-instance control server implementing the protocol commands:
ping, identify, state, dispatch, quit, restart, reload,
subscribe, and query.
Each running instance has a random per-session identity (derived from PID,
nanosecond time, and 8 bytes from /dev/urandom — not from PID alone) and an
isolated runtime directory under $XDG_RUNTIME_DIR/maverick/<session-id>/
(mode 0700), with the socket at control.sock. An identity record (a JSON
file) is written as <session-id>.json. Instance discovery
(maverickctl list) scans these directories, enriches each record with live
/proc data, and computes liveness by requiring both a responding socket and a
matching process start time — which protects against PID recycling. A real login
session and a Xephyr test instance therefore never share or fight over a socket.
- Hard restart:
restartre-executes the WM binary in place with the exact launch arguments (including--config/--name/--replace) it was started with, rebuilding all state from scratch. - Deterministic shutdown: quit runs with a bounded timeout (3 seconds). When the deadline is reached, any remaining clients are force-killed; shutdown does not depend on client cooperation to finish.
- Cooperative close: before forcing, the WM asks clients that support
WM_DELETE_WINDOWto close. - Compositor fallback: if GL is unavailable, already owned, or fails at init, Maverick stays on the non-composited X11 path.
- Client death handling:
DestroyNotify/UnmapNotifyremove windows and drop their compositor textures; a self-unmap guard avoids unmanaging the WM's own surfaces. - Stale state cleanup: discovery prunes dead instances; the session snapshot drops references to windows that are no longer alive; the control server safely removes stale socket files on startup.
Maverick does not claim to be crash-proof; these measures reduce the blast radius of failures and speed recovery.
Maverick separates a pure-logic core from the X11 backend. The flow of a state change is:
User action / IPC command
│
▼
Engine::dispatch(Action) ──► Vec<Effect>
│
▼
State ──► layout::arrange ──► present::present_into
│
▼
DesiredState (explicit hand-off)
│
▼
Reconciler ──► AppliedState ──► X11 protocol calls
│
▼
Compositor / presentation (OpenGL)
Engine::dispatch(Action) -> Vec<Effect>is the only path from a keybind or IPC command to state mutation.Effectis the vocabulary (e.g.ArrangeMonitor,FocusWindow,SetFullscreen) that the backend turns into X11 calls. A future non-X11 backend would implement the same effect execution without changing the core.- The Reconciler (
backend/x11/reconciler.rs) is the single owner of "what is actually on X11". It decides whether aConfigureWindow(or restack/focus) is actually needed, centralizing applied-state change detection. - Maverick keeps explicit state ownership and invariants:
State::check_invariants()verifies internal consistency, and runtime geometry is always reconstructed against the windows that are actually alive rather than trusted from disk. - The window manager and the GLX compositor share one X11 connection (the
x11rbXCB connection is passed to the hand-written GL FFI), avoiding a second display connection. - The core (
src/core/) contains no X11 code; all protocol interaction lives insrc/backend/x11/.
- A Linux (or other X11-compatible) system with an X server (such as X.Org). Maverick is an X11 window manager, not a standalone display server.
- A Rust toolchain, MSRV 1.82 (
rust-versioninCargo.toml), and Cargo. - The system X11 and X11-xcb client libraries are linked by the GLX FFI
(
maverick-gl); a C toolchain (linker) is therefore required to build the workspace. - OpenGL 3.3 capable GPU and driver for the compositor.
libGL.so.1is loaded at runtime viadlopenand is optional: if absent, the compositor is disabled and Maverick runs on the plain X11 path. - No
build.rs, nopkg-config; all Rust dependencies are either pure-Rust (x11rb) or implemented in-tree.
Maverick is a Cargo workspace. The root Cargo.toml is itself the maverick
package; --workspace is required to also build the library and binary crates
(maverick-sys, maverick-gl, maverick-dialog, maverick-toml,
maverick-img, maverick-installer).
git clone https://github.com/azytar/Maverick.git
cd Maverick
cargo build --release --workspaceBuilt binaries are placed in target/release/:
maverick— the window manager.maverickctl— the control/query CLI.maverick-msg— the verbatim command forwarder.maverick-dialog— the quit-confirmation dialog.maverick-installer— the installer (see Development).
Add the binaries to your PATH, or invoke them by absolute path.
From ~/.xinitrc:
exec maverickFor a display manager, install an X session file at
/usr/share/xsessions/maverick.desktop (the maverick-installer can create one
for you):
[Desktop Entry]
Name=maverick
Comment=Tiling window manager with built-in compositor
Exec=maverick
Type=XSessionMaverick accepts the following command-line options (all optional, in any order):
| Option | Description |
|---|---|
--config <path> |
Load the config TOML from <path> instead of the default location. The same path is reused on maverickctl reload and on restart, so a custom config survives both. |
--check-config [path] |
Parse the config (the --config path if given, otherwise the default) and exit. Exit code 0 means clean (no warnings or errors); 1 means warnings or errors were reported. The WM is never started. |
--replace / -r |
Replace an already-running window manager, adopting its windows. |
--name <id> |
Instance name used for control and discovery (so maverickctl can target the right instance). |
-v / --version |
Print version and exit. |
-h / --help |
Print usage and exit. |
Validate a config before starting:
maverick --check-config ~/.config/maverick/config.toml
maverick --config ~/.config/maverick/config.tomlConfiguration is optional. If no config file is found, Maverick runs on compiled-in defaults.
- Default location:
$XDG_CONFIG_HOME/maverick/config.toml, or$HOME/.config/maverick/config.tomlwhenXDG_CONFIG_HOMEis unset. - Format: TOML. Sections:
[general],[colors],[autostart],[wallpaper], and array sections[[keybindings]]and[[rules]]. - Fail-safe loading: a file with invalid TOML syntax is rejected whole and the compiled defaults are used; a single bad entry (unknown key, wrong type, bad action string) is dropped with a warning and the rest of the file still loads. Maverick never fails to start because of a bad config.
A full commented sample is at config/config.toml. Copy it as a starting point:
mkdir -p ~/.config/maverick
cp config/config.toml ~/.config/maverick/config.tomlApply changes without restarting:
maverickctl reload| Key | Type | Default | Notes |
|---|---|---|---|
border_width |
u32 | 2 |
Border width in pixels (border_w is accepted as an alias). |
gaps_inner |
u32 | 6 |
Gap between tiled windows. |
gaps_outer |
u32 | 6 |
Gap at the screen edges. (gaps sets both.) |
smart_gaps |
bool | false |
Collapse gaps to 0 when a workspace has a single tiled window. |
corner_radius |
u32 | 0 |
Rounded corner radius via X11 Shape. |
n_tags |
usize | 9 |
Number of workspaces (clamped to 1–9). |
column_width |
f32 | 0.6 |
Width of a new column as a fraction (0.1–1.0) of the workarea. |
accordion_boost |
f32 | 0.0 |
Extra fraction (0.0–0.9) the focused column expands. |
overview_zoom_min |
f32 | 0.25 |
Minimum Overview film-strip zoom (0.05–1.0). |
focus_mouse |
bool | false |
Focus a window when the pointer enters it. |
warp_cursor |
bool | false |
Warp the cursor to the centre of the focused window. |
auto_workspace_binds |
bool | true |
Auto-generate Super+1..9 / Super+Shift+1..9 workspace binds. |
theme |
string | — | Named colour preset (defaults to Catppuccin Mocha when unset). |
tag_names |
list | ["1".."9"] |
Cosmetic workspace names (addressed by index). |
compositor_enabled |
bool | true |
Master switch for the compositor. |
camera_stiffness |
f32 | 220.0 |
Scroll-camera spring stiffness. |
camera_damping |
f32 | 30.0 |
Scroll-camera spring damping. |
The compositor is configured under
[general]viacompositor_enabled(not a separate[compositor]table). It can also be disabled for a single run with theMAVERICK_NO_COMPOSITORenvironment variable.
All colours are 24-bit hex 0xRRGGBB. Keys normal/focused/urgent are
accepted alongside the older col_normal/col_focused/col_urgent aliases.
| Key | Default | Meaning |
|---|---|---|
normal |
0x45475a |
Unfocused window border. |
focused |
0x89b4fa |
Focused window border. |
urgent |
0xf38ba8 |
Urgent window border. |
Programs to launch once the WM is ready, as a list of argument lists:
[autostart]
commands = [["nm-applet"]]The compositor and wallpaper are built in and are not autostart entries; bars and
portals are launched here like any other program. Maverick ships no status bar — use any external status bar; it reserves screen space for any dock that
publishes _NET_WM_STRUT_PARTIAL/_NET_WM_STRUT, so tiled windows never overlap
it. For status text, Maverick exposes the root window name through
maverickctl state / maverickctl subscribe.
[wallpaper]
path = "~/Pictures/wallpaper.png" # image, or a .glsl/.frag shader; null disables
mode = "fill" # fill | fit | stretch | center- Images (PNG decoded natively; other formats via an external converter).
- GLSL shaders compiled on the GPU, redrawn every frame with
u_time,u_resolution,u_delta_time. - A
Videosource is reserved but not implemented.
Rules match by WM_CLASS (class/instance), window type, and title
(case-insensitive substring) and can force floating, sticky, workspace
assignment, opacity, border width, and fullscreen policy.
[[rules]]
class = "mpv"
float = true| Field | Type | Description |
|---|---|---|
class |
string | Match WM_CLASS (case-insensitive substring). |
instance |
string | Match the WM_CLASS instance. |
window_type / type |
string | Match EWMH window type (e.g. dialog). |
title |
string | Match window title (case-insensitive substring). |
float |
bool | Force floating. |
sticky |
bool | Keep visible across workspaces. |
workspace / ws |
int (1-based) | Pin the window to this workspace (1 = first). |
size |
[w, h] | Force size for floats. |
position |
[x, y] | Force position for floats. |
opacity |
float | Per-window opacity. |
border_width |
int | Override border width. |
ignore_initial_state |
bool | Ignore the client's initial maximized/fullscreen request. |
deny_fullscreen |
bool | Refuse client-requested fullscreen (user toggles still work). |
true_fullscreen |
bool | Treat as an exclusive, screen-covering fullscreen. |
Super is the Windows/Mod4 key. All bindings below are the compiled defaults
and are fully overridable in config.toml.
| Binding | Action |
|---|---|
Super+Return |
Terminal (alacritty) |
Super+P |
App launcher (rofi -show drun) |
Super+Shift+P |
Command runner (rofi -show run) |
| Binding | Action |
|---|---|
Super+Shift+C |
Kill focused window |
Super+Shift+Space |
Toggle floating |
Super+Shift+F |
Toggle fullscreen |
Super+Shift+M |
Toggle maximize |
Super+Shift+Q |
Quit (runs maverickctl quit --confirm) |
| Binding | Action |
|---|---|
Super+H / L / J / K |
Focus column left/right, window down/up |
Super+Shift+H / L / J / K |
Move window left/right, down/up |
Super+Tab |
Focus next monitor |
Super+Shift+Tab |
Move window to next monitor |
| Binding | Action |
|---|---|
Super+Shift+Return |
Move window to a new column |
Super+Ctrl+H |
Shrink focused column (−50 px) |
Super+Ctrl+L |
Grow focused column (+50 px) |
Super+Ctrl+J |
Collapse column into the one to its left |
| Binding | Action |
|---|---|
Super+Space |
Cycle layout modes |
Super+T |
Column layout |
Super+G |
Grid layout |
Super+O |
Toggle Overview |
Super+N |
Overview navigate right |
Super+Shift+O |
Overview navigate left |
Super+E |
Enter focused window in Overview |
Super+= / Super+- |
Zoom viewport in / out |
Super+] / Super+[ |
Page-snap scroll right / left |
Super+Shift+R / Super+F5 |
Restart in place |
| Binding | Action |
|---|---|
Super+1 … Super+9 |
Switch to workspace 1–9 |
Super+Shift+1 … Super+Shift+9 |
Move focused window to workspace 1–9 |
| Action | Result |
|---|---|
Super+Left-drag |
Move floating window |
Super+Right-drag |
Resize floating window |
Super+wheel |
Scroll the column ribbon |
| Drop floating on a tiled window | Re-insert it into the tiling tree |
maverickctl is the primary control client. It connects to the running instance
selected by --session <id>, --name <id>, the MAVERICK_INSTANCE
environment variable, or the current DISPLAY/TTY context.
| Subcommand | Description |
|---|---|
list / ls |
List discovered instances with identity and liveness. |
state |
Print the full window/state snapshot (JSON by default). |
query / q |
Print a condensed snapshot. |
msg / dispatch / command |
Send a control action (e.g. msg wallpaper set …). |
subscribe / sub |
Stream state-change events. |
quit |
Quit the instance. --confirm requires confirmation (the prompt uses maverick-dialog when available, falling back to zenity/kdialog/a TTY prompt); --yes/-y skips confirmation. |
quit-all |
Quit all discovered instances. |
restart |
Restart the instance in place. |
reload |
Re-read the config file live. |
prune |
Remove stale instance records. |
help / -h / --help |
Print usage. |
state/query also accept -j/--json and -b/--bare as pass-through
flags.
Examples:
maverickctl list
maverickctl state
maverickctl msg wallpaper set ~/Pictures/wallpaper.png
maverickctl msg wallpaper mode fit
maverickctl msg wallpaper clear
maverickctl subscribe
maverickctl quit --confirm
maverickctl restart
maverickctl reload
maverickctl prunemaverick-msg is a thin verbatim forwarder over the same engine. The examples
above also work as:
maverick-msg wallpaper set ~/Pictures/wallpaper.png
maverick-msg wallpaper mode fit
maverick-msg wallpaper clearUnit tests live in the crates (src/core/tests.rs, src/backend/x11/tests.rs):
cargo test --workspaceFormat and lint:
cargo fmt --all -- --check
cargo clippy --workspace --all-targetsClippy is not entirely clean: two deliberate
clippy::question_markwarnings remain inmaverick-sys/src/control.rs(documented inCHANGELOG.md). They are intentionally kept.
An integration harness under tests/ drives Maverick inside a nested Xephyr
server with real X clients and asserts live X properties. It is a manual/CI
harness and is not part of cargo test. Requirements: Xephyr, x11-utils,
xdotool, xterm (and optionally firefox/mpv). Run it with:
DISPLAY=:1 ./tests/xephyr-suite.shThe harness sets MAVERICK_NO_COMPOSITOR=1 because the GLX compositor cannot
initialize under a nested Xephyr display; this is a limitation of the test
environment, not of the compositor on a real X server.
Maverick is under active development. The X11 backend and the integrated OpenGL compositor are implemented and are the project's primary, daily-driven configuration.
- Backend: X11 (RandR, EWMH/ICCCM). No Wayland backend.
- Compositor: implemented (OpenGL 3.3 / GLX), enabled by default with automatic, safe fallback.
- Layouts: Column and Grid, both implemented.
- IPC: implemented (
maverickctl/maverick-msg, per-session socket). - Session management: implemented (per-instance identity, discovery, isolation, restart/reload).
The project is not declared production-ready; see Known Limitations.
- X11 only. There is no Wayland backend and Maverick requires a running X server.
- Partial redraw is conditional. It is only used when
GLX_EXT_buffer_ageis present and the back buffer still holds the previous frame; otherwise a full redraw is performed. - Compositor is optional by design. When disabled (or when GL is unavailable), Maverick runs without compositing; rounded corners then fall back to X11 Shape and translucency is not composited.
Videowallpaper is reserved but not implemented. Only image and GLSL- shader wallpapers work.- No blur or shadow effects. Only per-window opacity and rounded-corner rendering are provided.
- Tiled windows do not honor client
ConfigureRequestresizes. Tiled geometry is owned by the WM; resizing is done via column-width adjustment or by floating the window. - Workspace names are cosmetic. Workspaces are addressed by index.
n_tagsis clamped to the range 1–9.
The workspace is laid out as:
Maverick/
├── src/ entry point, configuration, and core logic
│ ├── main.rs signals, autostart, and control wiring
│ ├── config.rs compiled default configuration
│ ├── userconfig.rs optional TOML config parsing and merge
│ ├── types.rs core data model (State, Monitor, Workspace, Column, Client)
│ ├── core/ pure logic layer — no X11
│ │ ├── engine.rs Engine::dispatch(Action) -> Vec<Effect>
│ │ ├── layout.rs column and grid layout engines
│ │ ├── present.rs fullscreen / maximize presentation
│ │ ├── desired.rs DesiredState hand-off to the Reconciler
│ │ └── session.rs session persistence and recovery
│ └── backend/
│ └── x11/ the only X11-speaking code
│ ├── mod.rs WindowManager, event loop, RandR
│ ├── reconciler.rs single owner of applied geometry
│ ├── compositor.rs GL damage tracking and draw
│ └── framesched.rs frame scheduler
├── maverick-sys/ IPC, per-session identity, control server, discovery
├── maverick-gl/ hand-written GLX / OpenGL FFI
├── maverick-img/ dependency-free PNG decode
├── maverick-toml/ zero-dependency TOML parser
├── maverick-dialog/ standalone quit-confirmation window
├── maverick-installer/ installer (workspace member)
├── config/
│ └── config.toml full, commented sample configuration
├── tests/ Xephyr integration harness and C test clients
├── CHANGELOG.md
├── Cargo.toml workspace root and the `maverick` package
├── Cargo.lock
├── LICENSE
└── README.md
All crates use edition 2021 and the workspace MSRV is 1.82.
Building and testing (see above): cargo build --release --workspace,
cargo test --workspace, cargo fmt --all -- --check,
cargo clippy --workspace --all-targets.
Installing: the maverick-installer binary is the project's install
mechanism. It compiles the workspace, installs maverick, maverickctl,
maverick-msg, and maverick-dialog to /usr/local/bin (or ~/.local/bin as
a non-root user), and installs an X session desktop file. There are no
distribution packages (no Arch PKGBUILD or Debian .deb); run it with:
cargo build --release --workspace
./target/release/maverick-installerMaverick is distributed under the GNU General Public License version 3
(GPL-3.0). See LICENSE.