Table of Contents
- Architecture and philosophy
- The core rule
- What Rust should own
- What Lua should own
- Why this split matters
- The three runtime layers
- 1. Pure core library
- 2. Headless runtime
- 3. Live compositor runtime
- Why headless matters so much
- The shared canvas idea
- What a config is really doing
- Action surfaces in the runtime
- How the example configs fit into the architecture
- The current limits of the architecture
- Short version
- Read next
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Architecture and philosophy
This page explains how evil is structured and why the project is shaped the way it is.
The core rule
The single most important rule in the project is:
- Rust provides facts
- Lua provides policy
This is not just a slogan. It is the main design filter.
What Rust should own
Rust should be the authoritative source of:
- window existence and identity
- output existence and layout
- pointer and input facts
- protocol integration
- rendering
- validation of commands and draw data
- safe application of state changes
- lifecycle transitions like map / unmap / focus / close / destroy
Rust is where the compositor stays honest.
What Lua should own
Lua should decide how those facts become user experience.
Examples:
- which window gets focus when another window appears
- whether clicking empty space clears focus
- how dragging moves a window
- how resizing behaves
- how new windows are placed
- whether windows tile, float, or use page-style desktops
- what compositor-drawn borders or overlays look like
Lua is where the compositor becomes personal.
Why this split matters
Without that split, a compositor tends to drift toward one hardcoded personality.
With this split, the project can support very different workflows on the same core:
- floating window manager
- spatial canvas desktop
- tiling-like layout built in Lua
- automation-heavy personal shell
- hybrid setups that mix several ideas
That is the real long-term value of evil.
The three runtime layers
Today, evil is best understood as three connected systems.
1. Pure core library
This covers logic like:
- canvas math
- viewport transforms
- focus stack behavior
- placement
- rules
- move/resize helpers
- output layout
- binding parsing
This layer is the easiest to test and the least tied to a live compositor session.
2. Headless runtime
This is a deterministic runtime that does not need a live display.
It is useful for:
- Lua policy tests
- snapshot tests
- lifecycle tests
- trying API ideas before live validation
Headless is the best proving ground when the behavior does not require real protocols or real clients.
3. Live compositor runtime
This includes:
- nested
winit - early standalone
udev/ tty
This is where protocol handling, live input, real clients, and rendering all meet.
Why headless matters so much
The project philosophy depends on headless being trustworthy.
A lot of user-facing behavior should be testable there first:
- focus logic
- move / resize policy
- hook payload shape
- runtime snapshots
- action application
If headless and live drift badly, Lua policy becomes harder to reason about.
So the project tries to keep:
- shared hook semantics aligned
- shared snapshot meaning aligned
- shared commands aligned
unless a backend genuinely cannot support the same behavior.
The shared canvas idea
The canvas model is one of the main things that makes evil different.
Instead of thinking:
- “each monitor owns its own windows”
evil thinks more like:
- “windows live in one world”
- “outputs are views into that world”
So outputs act like cameras.
That is why features like:
- panning
- zooming
- screen-sized page emulation
- geometric output association
fit naturally into the project.
What a config is really doing
A Lua config is not just a bag of preferences. It is a small policy program.
A config can:
- define movement behavior
- define focus behavior
- define how bindings are interpreted
- define layout rules
- define compositor overlays
- keep Lua-owned state for features like page-style desktops or fake fullscreen
That is why examples are so important in this project.
Action surfaces in the runtime
Today, there are three important ways Lua can cause things to happen.
1. Built-in binding actions — simple action names like pan_left, spawn, close_window, focus_next. Convenient for evil.bind(...).
2. Imperative runtime commands — direct calls like evil.window.move(...), evil.window.focus(...), evil.canvas.pan(...). Good for quick or explicit hook logic.
3. Declarative returned actions — tables returned from hooks, like { kind = "move_window", id = ..., x = ..., y = ... }. Still supported, but current examples prefer imperative commands because they are easier to read for many users.
How the example configs fit into the architecture
The examples are not random demos. They are meant to show how very different policy ideas can sit on top of the same runtime.
Examples currently cover:
- a baseline floating/canvas config
- page-based tiling/workspace emulation
The tiling example is especially important because it shows that a workspace-like, tiling-like user experience can be built on top of the current canvas/runtime model without a first-class workspace subsystem yet.
The current limits of the architecture
The architecture is strong, but some things are still narrow or incomplete.
Examples:
- the tty backend still needs more hardening
- several protocol slices are only partly broadened
- some user-facing behaviors are still implemented creatively in Lua rather than as native runtime features
- the Lua API is useful but not yet “finished forever”
That is okay, as long as the project keeps support claims honest.
Short version
If you only remember three things from this page, remember these:
-
evilis trying to be a small compositor kernel + strong Lua policy layer -
the headless runtime matters because it keeps policy work testable and honest
-
the shared canvas model is not an accident — many of the project’s unusual workflows grow naturally from it
Read next
evil wiki
- Home
- Getting started
- Architecture and philosophy
- Configuration guide
- Writing your first config
- Lua basics for evil users
- Lua API guide
- Lua API cheat sheet
- Hooks guide
- Hook payload summary
- Queries, commands, and actions
- Drawing and visuals
- Example configs overview
- Example shared helpers and rules
- IPC protocol
- Testing and debugging
- Contributor validation matrix
- Feature status and limitations
- Feature and support matrix
- Recipes