Skip to main content
Version: 2.1 (dev)

Custom Layouts

You will learn

  • The minimal structure of a layout table
  • How to register and activate a custom layout
  • How to position clients using p.workarea and p.geometries
  • How to read tag properties like master_count and master_width_factor
  • When and how to skip gap application

A layout is a Lua table with a name and an arrange(p) function. The compositor calls arrange whenever it needs to position tiled clients on screen (window opened, closed, resized, tag properties changed). Your job is to write a geometry entry for each client. The compositor handles borders and gaps after your function returns.

The Minimal Layout​

The simplest useful layout gives every client the full workarea, like max:

local my_max = {
name = "my_max",
arrange = function(p)
for _, c in ipairs(p.clients) do
p.geometries[c] = {
x = p.workarea.x,
y = p.workarea.y,
width = p.workarea.width,
height = p.workarea.height,
}
end
end,
}

Every client gets the same rectangle. The focused one is drawn on top, so it looks like a maximized layout.

Register and Use It​

Add your layout to the default list so you can cycle to it:

tag.connect_signal("request::default_layouts", function()
awful.layout.append_default_layouts({
awful.layout.suit.tile,
awful.layout.suit.floating,
awful.layout.suit.max,
my_max, -- your layout is now in the cycle
})
end)

Or set it directly on a tag:

-- Set on a specific tag
local t = awful.screen.focused().selected_tag
t.layout = my_max

-- Set when creating tags
awful.tag({ "1", "2", "3" }, s, my_max)

Press your layout-cycle keybinding (default: Mod4+Space) to cycle through layouts and confirm yours appears.

Read the Workarea​

The p.workarea table gives you the usable screen rectangle. Wibars, struts, and screen padding are already subtracted:

local wa = p.workarea
-- wa.x, wa.y = top-left corner
-- wa.width = usable width
-- wa.height = usable height

You do not need to subtract borders or gaps. The compositor applies those after your arrange function returns.

A Practical Example: Centered Column​

This layout arranges all clients in a centered column at 60% of the screen width, splitting the height evenly:

local centered_column = {
name = "centered_column",
arrange = function(p)
local wa = p.workarea
local n = #p.clients
if n == 0 then return end

local col_width = math.floor(wa.width * 0.6)
local col_x = wa.x + math.floor((wa.width - col_width) / 2)
local client_height = math.floor(wa.height / n)

for i, c in ipairs(p.clients) do
p.geometries[c] = {
x = col_x,
y = wa.y + (i - 1) * client_height,
width = col_width,
height = client_height,
}
end
end,
}

With three clients on a 1920x1080 screen (after a 30px wibar), each window would be 1152px wide, centered horizontally, and 350px tall stacked vertically.

Using Tag Properties​

Layouts can read properties from p.tag to let users control behavior at runtime. The most common are master_count and master_width_factor. See Master and Stack for what each property does.

Here is the centered column layout extended to support a primary/secondary split:

local centered_split = {
name = "centered_split",
arrange = function(p)
local wa = p.workarea
local n = #p.clients
if n == 0 then return end

local nmaster = math.min(p.tag.master_count, n)
local nstack = n - nmaster
local mwfact = p.tag.master_width_factor

-- Master column: centered, takes mwfact of width
local master_w = math.floor(wa.width * mwfact)
local master_x = wa.x + math.floor((wa.width - master_w) / 2)

for i = 1, nmaster do
local h = math.floor(wa.height / nmaster)
p.geometries[p.clients[i]] = {
x = master_x,
y = wa.y + (i - 1) * h,
width = master_w,
height = h,
}
end

-- Stack: fill the full width below the master
if nstack > 0 then
local stack_h = math.floor(wa.height / nstack)
for i = 1, nstack do
p.geometries[p.clients[nmaster + i]] = {
x = wa.x,
y = wa.y + (i - 1) * stack_h,
width = wa.width,
height = stack_h,
}
end
end
end,
}

Users can now adjust the split with the standard keybindings: Mod4+l / Mod4+h to change master_width_factor, Mod4+Shift+l / Mod4+Shift+h to change master_count.

Skipping Gaps​

Add a skip_gap function when gaps don't make sense for your layout. The compositor calls skip_gap(nclients, tag) before applying useless_gap. Return true to suppress gaps.

my_max.skip_gap = function(nclients, tag)
return true -- max layout: clients overlap, gaps are meaningless
end

For layouts where a single client fills the screen, skip the gap only when there is one client:

centered_column.skip_gap = function(nclients, tag)
return nclients == 1
end

Tips​

  • Keep arrange pure. Only write to p.geometries. Do not call c:geometry(), emit signals, or produce other side effects.
  • Handle zero clients. p.clients can be empty. Return early with if #p.clients == 0 then return end.
  • Force a re-layout. Call awful.layout.arrange(screen) during development to trigger your layout without opening/closing windows.
  • Use math.floor. Pixel coordinates must be integers. Rounding errors from division can cause 1px gaps or overlaps.
  • Test edge cases. Try your layout with 1 client, 2 clients, and many clients. Resize the screen. Change master_count and master_width_factor to values you didn't expect.

See Also​