Skip to main content
Version: 2.0 (dev)

screen

The screen object represents a physical or virtual display. Each monitor connected to your system is a screen.

Upstream documentation: AwesomeWM screen docs

Accessing Screens

-- Primary screen
local s = screen.primary

-- Currently focused screen
local s = awful.screen.focused()

-- Iterate all screens
for s in screen do
print(s.index, s.geometry.width)
end

-- Get screen by index (1-based)
local s = screen[1]

-- Total screen count
local count = screen.count()

Properties

Geometry & Layout

PropertyTypeAccessDescription
geometrytablereadScreen bounds {x, y, width, height} in logical pixels
workareatablereadUsable area after panels/struts {x, y, width, height}
indexnumberread1-based screen index
paddingtableread/writeManual padding {top, right, bottom, left}

Output Information

PropertyTypeAccessDescription
outputstablereadPhysical output info (see below)
namestringread/writeUser-assignable screen name

The outputs table is keyed by output name:

-- Example outputs table
{
["DP-1"] = {
name = "DP-1",
mm_width = 600, -- Physical width in millimeters
mm_height = 340, -- Physical height in millimeters
viewport_id = 1,
}
}

Scaling & DPI somewm-only

PropertyTypeAccessDescription
scalenumberread/writeOutput scale factor (0.1 to 10.0)
dpinumberreadScreen DPI (computed from physical size)
minimum_dpinumberreadLowest DPI across outputs
maximum_dpinumberreadHighest DPI across outputs
preferred_dpinumberreadRecommended DPI for this screen
-- Get current scale
print(screen.primary.scale) -- e.g., 1.5

-- Set fractional scale for HiDPI display
screen.primary.scale = 1.5

-- Check DPI
print(screen.primary.dpi) -- e.g., 144

When you change scale, the geometry and workarea update to reflect logical pixel dimensions. For example, a 3840x2160 display at scale 2.0 reports geometry of 1920x1080.

Tags & Clients

PropertyTypeAccessDescription
tagstablereadAll tags on this screen
selected_tagstablereadCurrently visible tags
selected_tagtagreadFirst selected tag (or nil)
clientstablereadAll clients on this screen
tiled_clientstablereadNon-floating, non-minimized clients

Lifetime

PropertyTypeAccessDescription
validbooleanreadfalse once the screen has been removed

A screen object outlives the monitor it came from: your handlers, timers and closures can still hold one after the monitor is unplugged or fake_remove() is called. Once that happens, valid is the only property you can read. Every other read raises invalid object, and screen[s] gives you nil.

Check it before touching a screen you captured earlier:

gears.timer.delayed_call(function()
if not s.valid then return end
-- safe to use s here
end)

Methods

Screen Management

-- Get total screen count
local count = screen.count()

-- Get bounding geometry (respects workarea and padding)
local geo = s:get_bounding_geometry({
honor_workarea = true,
honor_padding = true,
})

-- Swap two screens' positions
screen[1]:swap(screen[2])

Virtual Screens

-- Create a virtual screen
local fake = screen.fake_add(0, 0, 1920, 1080)

-- Resize a virtual screen
fake:fake_resize(0, 0, 2560, 1440)

-- Remove a virtual screen
fake:fake_remove()

Signals

SignalArgumentsDescription
addedsNew screen connected
removedsScreen disconnected
list(none)Screen list changed
primary_changedsPrimary screen changed
property::geometrys, old_geometryScreen geometry changed
property::workareas, old_workareaWorkarea changed (panel added/removed)
property::scalesOutput scale changed somewm-only
property::outputssOutput metadata changed
property::indexsScreen index changed

property::geometry and property::workarea pass the previous value as a second argument, so you can work out what moved rather than only that something did:

screen.connect_signal("property::geometry", function(s, old_geometry)
local dx = s.geometry.x - old_geometry.x
local dy = s.geometry.y - old_geometry.y
-- dx/dy is how far the screen moved
end)

When a monitor is plugged or unplugged, added and removed reach you immediately, while the others are queued and arrive at the next frame. So inside an added handler the screen already exists, but list has not fired yet. See Signals Reference: Dispatch for the full list.

Example: React to Scale Changes

screen.connect_signal("property::scale", function(s)
-- Refresh widgets when scale changes
print("Screen " .. s.index .. " scale is now " .. s.scale)
end)

Example: Handle Monitor Hotplug

screen.connect_signal("added", function(s)
-- Set up new monitor with appropriate scale
if s.geometry.width > 3000 then
s.scale = 1.5 -- 4K display
end

-- Create tags
awful.tag({ "1", "2", "3" }, s, awful.layout.layouts[1])
end)

Common Patterns

Auto-Scale Based on Resolution

awful.screen.connect_for_each_screen(function(s)
if s.geometry.width > 3000 then
s.scale = 1.5
elseif s.geometry.width > 2000 then
s.scale = 1.25
else
s.scale = 1.0
end
end)

Get Screen Under Mouse

local s = awful.screen.focused()  -- Follows focus
-- or
local s = mouse.screen -- Screen containing mouse pointer

Find Screen by Output Name

for s in screen do
for name, _ in pairs(s.outputs) do
if name == "HDMI-A-1" then
return s
end
end
end

See Also