Multi-Monitor Setup
SomeWM handles multiple monitors automatically. Each monitor becomes a screen object with its own tags, wibar, and workspace.
Screen Detection
When SomeWM starts, it automatically detects all connected monitors. Each becomes a screen:
-- Print all connected screens
for s in screen do
print("Screen " .. s.index .. ": " .. s.geometry.width .. "x" .. s.geometry.height)
end
Accessing Screens
-- The primary screen (usually the first one)
local primary = screen.primary
-- Get screen by index (1-based)
local first = screen[1]
local second = screen[2]
-- The screen with keyboard focus
local focused = awful.screen.focused()
-- Iterate all screens
for s in screen do
-- Do something with each screen
end
Per-Screen Setup
The default config uses connect_for_each_screen to set up tags and wibars for every screen. This runs once for each existing screen, and again whenever a new screen is added.
awful.screen.connect_for_each_screen(function(s)
-- Create tags for this screen
awful.tag({"1", "2", "3", "4", "5"}, s, awful.layout.suit.tile)
-- Create a wibar for this screen
s.mywibar = awful.wibar {
position = "top",
screen = s,
widget = {
layout = wibox.layout.align.horizontal,
{ -- Left
layout = wibox.layout.fixed.horizontal,
s.mytaglist,
},
s.mytasklist, -- Middle
{ -- Right
layout = wibox.layout.fixed.horizontal,
wibox.widget.textclock(),
},
},
}
end)
Why connect_for_each_screen?
Using this pattern instead of a simple loop ensures:
- Each screen gets set up once
- New screens (hotplug) automatically get configured
- The setup code stays DRY
Moving Windows Between Screens
Via Keybindings
The default config includes keybindings to move windows between screens:
awful.keyboard.append_global_keybindings({
-- Move focused window to next screen
awful.key({ modkey, "Shift" }, "o", function()
if client.focus then
client.focus:move_to_screen()
end
end, { description = "move to next screen", group = "screen" }),
-- Focus next screen
awful.key({ modkey }, "o", function()
awful.screen.focus_relative(1)
end, { description = "focus next screen", group = "screen" }),
})
Programmatically
-- Move client to a specific screen
c:move_to_screen(screen[2])
-- Move client to next screen (cycles)
c:move_to_screen()
-- Move client to screen by direction
c:move_to_screen(c.screen:get_next_in_direction("right"))
Screen Geometry
Each screen has geometry properties:
local s = awful.screen.focused()
-- Full screen area
print(s.geometry.x, s.geometry.y) -- Position
print(s.geometry.width, s.geometry.height) -- Size
-- Workarea (minus panels/struts)
print(s.workarea.x, s.workarea.y)
print(s.workarea.width, s.workarea.height)
The workarea is the usable space after subtracting wibars and other panels.
Handling Hotplug
When monitors are connected or disconnected, SomeWM emits signals:
Screen Added
When a new monitor is connected:
screen.connect_signal("added", function(s)
-- Set up the new screen
-- Note: connect_for_each_screen handles this automatically
print("New screen: " .. s.index)
end)
Screen Removed
When a monitor is disconnected, windows need somewhere to go:
screen.connect_signal("removed", function(s)
-- Windows are automatically moved to another screen
print("Screen removed: " .. s.index)
end)
By default, windows from a removed screen migrate to another available screen. You can customize this:
-- Custom handling for screen removal
tag.connect_signal("request::screen", function(t)
-- Called when a tag's screen is removed
-- Find a new home for this tag
for s in screen do
if s ~= t.screen then
t.screen = s
return
end
end
end)
Different Configurations Per Screen
Different Tags Per Screen
awful.screen.connect_for_each_screen(function(s)
if s == screen.primary then
-- Primary screen: work-focused tags
awful.tag({"code", "web", "mail", "chat", "music"}, s, awful.layout.suit.tile)
else
-- Secondary screen: reference/monitoring
awful.tag({"docs", "term", "logs"}, s, awful.layout.suit.tile)
end
end)
Different Layouts Per Screen
awful.screen.connect_for_each_screen(function(s)
local layouts = s == screen.primary
and { awful.layout.suit.tile, awful.layout.suit.max }
or { awful.layout.suit.fair, awful.layout.suit.floating }
awful.tag({"1", "2", "3"}, s, layouts)
end)
Wibar on Primary Only
awful.screen.connect_for_each_screen(function(s)
awful.tag({"1", "2", "3"}, s, awful.layout.suit.tile)
if s == screen.primary then
s.mywibar = awful.wibar {
position = "top",
screen = s,
-- ...
}
end
end)
Common Multi-Monitor Patterns
Laptop + External Monitor
awful.screen.connect_for_each_screen(function(s)
-- Assume laptop is smaller, use it for monitoring
local is_laptop = s.geometry.width < 1920
if is_laptop then
awful.tag({"term", "logs"}, s, awful.layout.suit.tile.bottom)
else
awful.tag({"1", "2", "3", "4", "5"}, s, awful.layout.suit.tile)
end
end)
Spawn Apps on Specific Screen
-- Rule to start Firefox on screen 2
ruled.client.append_rule {
rule = { class = "firefox" },
properties = {
screen = 2,
tag = "web",
},
}
Focus Follows Monitor
When switching to a tag, also focus its screen:
tag.connect_signal("property::selected", function(t)
if t.selected then
t.screen:emit_signal("request::activate", "tag_switch", { raise = false })
end
end)
Troubleshooting
Screens Not Detected
Check that your displays are connected and recognized by the system:
# Check Wayland outputs
somewm-client screen list
# Or use wlr-randr
wlr-randr
Wrong Screen Order
Screen indices are assigned in the order they're detected. Use geometry or names to identify specific screens:
-- Find screen by position (leftmost)
local left_screen = nil
for s in screen do
if not left_screen or s.geometry.x < left_screen.geometry.x then
left_screen = s
end
end
Windows on Wrong Screen After Hotplug
Use rules to ensure specific apps always start on the right screen:
ruled.client.append_rule {
rule = { class = "Slack" },
properties = { screen = screen.primary },
}
See Also
- The Object Model - Understanding screens and clients
- Fractional Scaling - HiDPI configuration per screen
- AwesomeWM Screen Docs - Full screen API