Skip to main content
Version: 2.0 (dev)

React to client lifecycle

A common task: do something when a client appears, when focus changes, when a window closes. The mechanism is client.connect_signal. This guide shows the most useful client signals, with worked examples and gotchas.

For the full list, see client signals. For the conceptual model, see Signals (concepts).

Run code when a client appears

client.manage fires when a client is fully managed: tagged, placed, with rules applied. It's the standard hook for "do X to new windows".

client.connect_signal("manage", function(c)
-- Center floating windows on the focused screen
if c.floating then
awful.placement.centered(c)
end
end)

If you need to do something before rules apply (e.g. set a property the rules will then act on), use request::manage instead. The default rule handlers run in request::manage, so connect-order matters: handlers connected before awful.permissions and ruled.client are loaded see the pre-rule state.

Pin a scratchpad

A scratchpad is a window you toggle in and out, always on the same tag, always floating, ignored by tasklists. Set it up on manage:

client.connect_signal("manage", function(c)
if c.instance == "scratchpad" then
c.floating = true
c.skip_taskbar = true
c.sticky = false
c:move_to_tag(screen.primary.tags[5])
awful.placement.centered(c)
end
end)

Better: do this with a ruled.client rule. manage works, but rules are the structured way to express "every Foo client gets these properties". Use manage when the logic doesn't fit a rule (depends on runtime state, needs to read other clients).

React to focus changes

-- Highlight the focused client with a colored border
client.connect_signal("focus", function(c)
c.border_color = "#88c0d0"
end)

client.connect_signal("unfocus", function(c)
c.border_color = "#3b4252"
end)

focus fires after the new client has focus; unfocus fires before focus on the new client. So unfocus(old) → focus(new) is the order.

A note on the border-color example: if awful.permissions is handling borders for you, this fights with request::border. Either don't connect focus/unfocus for borders (let request::border do it), or replace the default request::border handler. See Replace a default handler.

React to property changes

Each property has its own signal. Read the new value off the client inside the handler:

-- Log title changes
client.connect_signal("property::name", function(c)
print(c.name)
end)

-- React to floating toggle
client.connect_signal("property::floating", function(c)
if c.floating then
awful.placement.centered(c)
end
end)

-- Show a notification when a client becomes urgent
client.connect_signal("property::urgent", function(c)
if c.urgent then
naughty.notify {
title = "Urgent: " .. (c.class or "?"),
message = c.name,
}
end
end)

property::urgent fires for both urgent = true and urgent = false. Always check c.urgent inside the handler.

Run code when a client closes

unmanage fires when a client is gone. Don't access geometry, screen, or tags inside the handler. They may already be invalid.

client.connect_signal("unmanage", function(c)
print("Closed: " .. (c.class or "?"))
end)

If you need state from the client at close time, save it on manage:

local client_data = {}

client.connect_signal("manage", function(c)
client_data[c] = { class = c.class, opened_at = os.time() }
end)

client.connect_signal("unmanage", function(c)
local data = client_data[c]
if data then
print(string.format("%s ran for %d seconds", data.class, os.time() - data.opened_at))
client_data[c] = nil
end
end)

Tables keyed by client object are safe. Once the client is unmanaged, drop your entry to avoid leaking.

Common patterns

WantSignalNotes
Do X to new windowsmanageUse ruled.client rules where they fit
Do X before rules applyrequest::manageConnect order matters
Highlight focused windowfocus / unfocusOr replace request::border
React to title changesproperty::nameUseful for tasklists
Save / restore window statemanage + unmanageStash data keyed by client
Notify on urgentproperty::urgentCheck c.urgent inside
Flash on first paintproperty::geometryFilter on first emit per client

See also