Skip to main content

Tag Persistence somewm-only

Tag persistence saves and restores tag state across monitor hotplug. The save handler lives in awful.permissions (connected automatically), and the restore handler lives in somewmrc.lua. Both are implemented in Lua, not in the C core.

Signals

Two signals drive the save/restore cycle:

tag request::screen

Fired when a tag's screen is about to be removed.

ArgumentTypeDescription
ttagThe tag losing its screen
reasonstringWhy the screen is being lost ("removed" on disconnect)

The default handler is awful.permissions.tag_screen. It checks reason == "removed" and saves the tag's metadata into awful.permissions.saved_tags, keyed by t.screen.output.name.

tip

The default handler is a named function connected automatically. To replace it, disconnect it first, then connect your own:

tag.disconnect_signal("request::screen", awful.permissions.tag_screen)
tag.connect_signal("request::screen", function(t, reason)
-- your custom handler
end)

If no handler is connected, tags will be garbage collected when their screen is removed.

screen request::desktop_decoration

Fired when a new screen is created and needs tags and wibars.

ArgumentTypeDescription
sscreenThe new screen

The default handler checks for saved state matching s.output.name. If found, it restores saved tags. Otherwise, it creates default tags "1" through "9".

Saved State Structure

Each tag is saved as a table with these fields:

FieldTypeDescription
namestringTag name
selectedbooleanWhether the tag was selected (visible)
layoutfunctionLayout algorithm (awful.layout.suit.*)
master_width_factornumberMaster area width ratio (0.0 to 1.0)
master_countnumberNumber of master windows
gapnumberSpacing between tiled clients in pixels
clientstableArray of client objects that were on this tag

Tags are grouped by connector name in awful.permissions.saved_tags:

-- awful.permissions.saved_tags structure
awful.permissions.saved_tags = {
["HDMI-A-1"] = {
{ name = "1", selected = true, layout = ..., clients = {...}, ... },
{ name = "2", selected = false, layout = ..., clients = {...}, ... },
-- ...
},
["DP-2"] = {
-- ...
},
}

Restore Behavior

The restore handler uses a two-pass approach so that clients on multiple tags keep their full tag list:

  1. Pass 1: For each saved tag entry, awful.tag.add() creates the tag with the saved properties. Valid clients are collected into a per-client tag list.
  2. Pass 2: Each client is moved to the screen once via c:move_to_screen(s), then assigned all its tags at once via c:tags(tags).

After restore, the saved state for that connector is cleared (awful.permissions.saved_tags[output_name] = nil).

Default Implementation

The save handler lives in awful.permissions and is connected automatically:

-- awful/permissions/init.lua

awful.permissions.saved_tags = {}

function awful.permissions.tag_screen(t, context)
if context ~= "removed" then return end
local s = t.screen
local output_name = s and s.output and s.output.name
if not output_name then return end
if not awful.permissions.saved_tags[output_name] then
awful.permissions.saved_tags[output_name] = {}
end
table.insert(awful.permissions.saved_tags[output_name], {
name = t.name,
selected = t.selected,
layout = t.layout,
master_width_factor = t.master_width_factor,
master_count = t.master_count,
gap = t.gap,
clients = t:clients(),
})
end

tag.connect_signal("request::screen", awful.permissions.tag_screen)

The restore handler lives in somewmrc.lua's request::desktop_decoration. It uses a two-pass approach to correctly preserve clients that belong to multiple tags:

-- somewmrc.lua

screen.connect_signal("request::desktop_decoration", function(s)
local output_name = s.output and s.output.name
local restore = output_name and awful.permissions.saved_tags[output_name]
if restore then
awful.permissions.saved_tags[output_name] = nil
-- Pass 1: recreate tags and build per-client tag lists
local client_tags = {}
for _, td in ipairs(restore) do
local t = awful.tag.add(td.name, {
screen = s,
layout = td.layout,
master_width_factor = td.master_width_factor,
master_count = td.master_count,
gap = td.gap,
selected = td.selected,
})
for _, c in ipairs(td.clients) do
if c.valid then
if not client_tags[c] then
client_tags[c] = {}
end
table.insert(client_tags[c], t)
end
end
end
-- Pass 2: move clients and assign full tag lists
for c, tags in pairs(client_tags) do
c:move_to_screen(s)
c:tags(tags)
end
else
awful.tag({ "1", "2", "3", "4", "5", "6", "7", "8", "9" },
s, awful.layout.layouts[1])
end
-- ... wibar setup follows
end)

These output properties are used for keying saved state:

PropertyTypeDescription
output.namestringConnector name (e.g. "HDMI-A-1", "eDP-1")
output.makestring or nilMonitor manufacturer
output.modelstring or nilMonitor model
output.serialstring or nilMonitor serial number

See the output reference for the full property list.

See Also