1
0
Fork 0
6 Hooks Guide
Agent edited this page 2026-05-26 05:36:15 -04:00

Hooks guide

Hooks are where most evil policy lives.

If you think of the config as “the part that gives the compositor its personality”, hooks are the most important tool.


What a hook is

A hook is a function assigned to evil.on.<name>.

Example:

evil.on.window_mapped = function(ctx)
  evil.window.focus(ctx.window.id)
end

When the matching event happens, the compositor calls your function and gives it a context table named ctx.


Hooks available today

Focus / placement / lifecycle

  • evil.on.resolve_focus
  • evil.on.place_window
  • evil.on.window_mapped
  • evil.on.window_unmapped
  • evil.on.focus_changed
  • evil.on.window_property_changed

Movement / resize lifecycle

  • evil.on.move_begin
  • evil.on.move_update
  • evil.on.move_end
  • evil.on.resize_begin
  • evil.on.resize_update
  • evil.on.resize_end

Input hooks

  • evil.on.key
  • evil.on.gesture

Draw hooks

  • evil.on.draw_background
  • evil.on.draw_window_overlay
  • evil.on.draw_overlay

resolve_focus

This is one of the most important hooks in the whole system.

It answers questions like:

  • what should be focused when a window appears?
  • what should be focused when the user clicks a window?
  • what should happen when the user clicks empty space?
  • what should happen when a focused window disappears?

Common ctx.reason values

  • "window_mapped"
  • "pointer_button"
  • "window_unmapped"

Simple example

evil.on.resolve_focus = function(ctx)
  if ctx.reason == "window_mapped" and ctx.window then
    evil.window.focus(ctx.window.id)
    return
  end

  if ctx.reason == "pointer_button" and ctx.window then
    evil.window.focus(ctx.window.id)
    return
  end

  if ctx.reason == "pointer_button" and not ctx.window then
    evil.window.clear_focus()
  end
end

Modifier-drag example

The shared helper in examples/lib/common.lua uses resolve_focus for things like:

  • focus clicked window
  • start interactive move on modifier + left click
  • start interactive resize on modifier + right click

So resolve_focus is often more than focus. It can also be a policy router for pointer-button workflows.


place_window

This hook decides where a newly created window should go.

Good uses:

  • simple tiling placement
  • putting windows on page-like regions
  • placing near the focused window

Example:

evil.on.place_window = function(ctx)
  evil.window.move(ctx.window.id, 100, 100)
end

window_mapped

This hook runs when a window becomes mapped.

Common uses:

  • focus the new window
  • run a first relayout
  • assign Lua-owned metadata/state
  • place the window relative to others

Example:

evil.on.window_mapped = function(ctx)
  evil.window.focus(ctx.window.id)
  evil.window.move(ctx.window.id, 200, 120)
end

window_unmapped

This hook runs when a window disappears from the active mapped set.

Common uses:

  • choose another window to focus
  • clear Lua-owned state for that window
  • rebuild a tiling layout

Example:

evil.on.window_unmapped = function(ctx)
  for index = #ctx.state.windows, 1, -1 do
    local window = ctx.state.windows[index]
    if not window.exclude_from_focus then
      evil.window.focus(window.id)
      return
    end
  end
  evil.window.clear_focus()
end

focus_changed

This hook runs after focus changes.

Common uses:

  • relayout based on the focused window
  • keep the camera on the focused window's page
  • update Lua-owned mode state

Move / resize hooks

These are the interactive hooks.

Move hooks

  • move_begin
  • move_update
  • move_end

Resize hooks

  • resize_begin
  • resize_update
  • resize_end

Common context fields

For movement:

  • ctx.window
  • ctx.dx
  • ctx.dy
  • ctx.pointer

For resize:

  • ctx.window
  • ctx.dx
  • ctx.dy
  • ctx.pointer
  • ctx.edges

Freeform move example

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

Resize example

evil.on.resize_update = function(ctx)
  evil.window.resize(ctx.window.id, ctx.window.w + ctx.dx, ctx.window.h + ctx.dy)
end

key

This hook runs for resolved key input.

Useful fields often include:

  • ctx.keyspec
  • ctx.key
  • ctx.bound_action
  • ctx.action
  • ctx.has_binding
  • ctx.modifiers
  • ctx.pointer

Common uses:

  • replace built-in key behavior with custom policy
  • page / workspace-like navigation
  • mode toggles
  • spawning commands with evil.spawn(...) or, when useful, returned actions

Example:

evil.on.key = function(ctx)
  if ctx.keyspec == "Super+Return" then
    evil.spawn("foot")
  end
end

gesture

This hook runs for swipe / pinch events.

Common uses:

  • respond to gestures in a custom way
  • cancel or counteract default behavior in certain profiles
  • build page-style navigation or custom zoom policy

Draw hooks

Draw hooks are different from the others. They are read-only and should return draw commands, not state-changing actions.

Use them for:

  • backgrounds
  • outlines
  • overlays
  • debug visuals

For more detail, read Drawing and visuals.


Good hook-writing habits

Good habits

  • keep helper functions small
  • name helpers clearly
  • return actions when that makes the hook clearer
  • use nil when the hook has nothing to do
  • keep draw hooks read-only

Habits that make hooks harder to read

  • giant anonymous hooks with several unrelated jobs
  • lots of repeated math instead of helpers
  • mixing layout logic, focus logic, and drawing logic in one place