Skip to main content
Version: 2.1 (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​

Not functional

screen.fake_add creates a real screen object: screen.count() goes up, added fires, and tags and wibars get created for it. But the screen does not work as a screen. Clients moved to it are hidden, wibars cannot attach to it, and layouts never run in its region, because window management is keyed to physical monitors (#561). Making a carved-out monitor region behave as a screen is planned as part of the 2.2 layout work.

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

-- Resize it
fake:fake_resize(0, 0, 2560, 1440)

-- Remove it
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​