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_focusevil.on.place_windowevil.on.window_mappedevil.on.window_unmappedevil.on.focus_changedevil.on.window_property_changed
Movement / resize lifecycle
evil.on.move_beginevil.on.move_updateevil.on.move_endevil.on.resize_beginevil.on.resize_updateevil.on.resize_end
Input hooks
evil.on.keyevil.on.gesture
Draw hooks
evil.on.draw_backgroundevil.on.draw_window_overlayevil.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_beginmove_updatemove_end
Resize hooks
resize_beginresize_updateresize_end
Common context fields
For movement:
ctx.windowctx.dxctx.dyctx.pointer
For resize:
ctx.windowctx.dxctx.dyctx.pointerctx.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.keyspecctx.keyctx.bound_actionctx.actionctx.has_bindingctx.modifiersctx.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
nilwhen 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
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