1
0
Fork 0
6 Configuration Guide
Agent edited this page 2026-05-26 05:47:03 -04:00

Configuration guide

|This page explains how an evil config is usually structured and how to think about building your own.


The simplest mental model

A config file is just a Lua program that usually does these jobs:

  1. load helpers with include(...)
  2. define local helper functions
  3. call evil.config({...})
  4. declare autostart commands
  5. declare key bindings
  6. assign hooks

That is the main pattern used by the shipped examples.


A good config order is:

1. Includes and shared modules

local common = include("lib/common.lua")
local shared_rules = include("rules.lua")
local commands = common.commands

2. Constants / user-tunable settings

local GAP = 24
local GRID_SIZE = 64

3. Small local helper functions

local function snap_to_grid(value)
  return math.floor((value / GRID_SIZE) + 0.5) * GRID_SIZE
end

4. evil.config({...})

evil.config({
  backend = "winit",
  canvas = {
    min_zoom = 0.2,
    max_zoom = 4.0,
  },
})

5. Autostart + binds

evil.autostart(commands.terminal)
evil.bind("Super+Return", "spawn", { command = commands.terminal })

6. Hook assignments

evil.on.move_update = function(ctx)
  ...
end

This order makes the file easier to scan.


include(...)

Use include(...) to split configs into smaller files.

Example:

local common = include("lib/common.lua")

Important facts:

  • includes are relative to the config root
  • includes are intentionally kept inside that root
  • you can use includes to separate shared helpers, rules, or layout logic

evil.config({...}) in practice

The config table sets the main runtime settings.

The most common sections are:

  • backend
  • canvas
  • draw
  • window
  • placement
  • tty
  • rules

backend

Use:

  • "winit" for nested live sessions
  • "headless" for headless runtime use
  • "udev" for tty / standalone experiments

canvas

Controls camera behavior.

Useful fields:

  • min_zoom
  • max_zoom
  • zoom_step
  • pan_step
  • allow_pointer_zoom
  • allow_middle_click_pan
  • allow_gesture_navigation

draw

Controls compositor-drawn visuals.

Useful fields:

  • stack
  • clear_color

window

Controls a few baseline Rust-side window facts.

Useful fields:

  • use_client_default_size
  • remember_sizes_by_app_id
  • hide_client_decorations

placement

Fallback placement used when no Lua placement hook overrides it.

Useful fields:

  • default_size
  • padding
  • cascade_step

tty

TTY-only settings.

Useful fields:

  • quit_keyspec
  • vt_switch_modifiers
  • output_layout

rules

Rules are the simplest way to change startup behavior for certain windows.

Example:

rules = {
  { app_id = "foot", floating = true, size = { w = 900, h = 600 } },
  { title_contains = "scratch", exclude_from_focus = true },
}

Autostart

Use evil.autostart(command) for programs that should run when the compositor starts.

Example:

evil.autostart(commands.terminal)

Keep in mind:

  • commands run through a shell
  • that is intentional so simple shell syntax works
  • because of that, keep commands readable and trusted

Bindings

Use evil.bind(keyspec, action, opts?).

Examples:

evil.bind("Super+Return", "spawn", { command = commands.terminal })
evil.bind("Super+Q", "close_window")
evil.bind("Super+H", "pan_left", { amount = 32 })

Good binding groups

It helps to group bindings by purpose:

  • launchers / apps
  • close / session control
  • canvas movement
  • workspace/page control
  • mode toggles

That makes the config easier to edit later.


Hooks

Hooks are where most custom behavior lives.

Common pattern:

evil.on.move_update = function(ctx)
  evil.window.move(ctx.window.id, ctx.window.x + ctx.dx, ctx.window.y + ctx.dy)
end

A good config usually defines helper functions first, then assigns those helpers to hooks.

Example:

local function move_window_with_pointer_delta(ctx)
  evil.window.move(ctx.window.id, ctx.window.x + ctx.dx, ctx.window.y + ctx.dy)
end

evil.on.move_update = move_window_with_pointer_delta

That is easier to read than putting all logic inside one giant anonymous function.


Good beginner strategy for writing a config

If you are new, do not try to invent a whole config from scratch.

A better path is:

  1. copy examples/tty-baseline.lua
  2. run --check-config
  3. change one bind
  4. change one hook
  5. test again
  6. repeat

That keeps problems small and understandable.


When to split a config into several files

Split when:

  • one file is getting too long
  • you want shared helpers
  • you want a reusable rule file
  • you want to separate layout logic from startup/binds

Good candidates for separate files:

  • lib/common.lua
  • rules.lua
  • layout.lua
  • draw.lua

Common config mistakes

  • putting too much logic in one anonymous hook
  • not using local
  • mixing unrelated binds into one block with no comments
  • trying to build a giant perfect config all at once
  • forgetting that some runtime facts are still intentionally narrow or provisional