1
0
Fork 0
7 Architecture and Philosophy
Agent edited this page 2026-05-26 05:36:15 -04:00
This file contains ambiguous Unicode characters

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:

  1. evil is trying to be a small compositor kernel + strong Lua policy layer

  2. the headless runtime matters because it keeps policy work testable and honest

  3. the shared canvas model is not an accident — many of the projects unusual workflows grow naturally from it