awesome/tests/_multi_screen.lua

513 lines
14 KiB
Lua
Raw Normal View History

-- Helper module to replicate a set of steps across multiple screen scenarios
-- Ideas for improvements:
-- * Export an iterator so the suits can call the screen change step by step
-- themselves instead of the current all or nothing
-- * Offer multiple scenarios such as screen add, remove, resize/swap instead
-- of having to test all corner cases all the time.
local wibox = require("wibox")
local surface = require("gears.surface")
local wallpaper = require("gears.wallpaper")
local color = require("gears.color")
local shape = require("gears.shape")
local module = {}
-- Get the original canvas size before it is "lost"
local canvas_w, canvas_h = root.size()
local half_w, half_h = math.floor(canvas_w/2), math.floor(canvas_h/2)
local quarter_w = math.floor(canvas_w/4)
-- Create virtual screens.
-- The steps will be executed on each set of screens.
local dispositions = {
-- A simple classic, 2 identical screens side by side
{
function()
return {
x = 0,
y = 0,
width = half_w,
height = canvas_h,
}
end,
function()
return {
x = half_w,
y = 0,
width = half_w,
height = canvas_h,
}
end,
},
-- Take the previous disposition and swap the screens
{
function()
return {
x = half_w,
y = 0,
width = half_w,
height = canvas_h,
}
end,
function()
return {
x = 0,
y = 0,
width = half_w,
height = canvas_h,
}
end,
keep = true,
},
-- Take the previous disposition and resize the leftmost one
{
function()
return {
x = quarter_w,
y = 0,
width = 3*quarter_w,
height = canvas_h,
}
end,
function()
return {
x = 0,
y = 0,
width = quarter_w,
height = half_h,
}
end,
keep = true,
},
-- Take the previous disposition and remove the leftmost screen
{
function()
return {
x = quarter_w,
y = 0,
width = 3*quarter_w,
height = canvas_h,
}
end,
function()
return nil
end,
keep = true,
},
-- Less used, but still quite common vertical setup
{
function()
return {
x = 0,
y = 0,
width = canvas_w,
height = half_h,
}
end,
function()
return {
x = 0,
y = half_h,
width = canvas_w,
height = half_h,
}
end
},
-- Another popular setup, 22" screens with a better 31" in the middle.
-- So, 2 smaller vertical screen with a larger central horizontal one.
{
-- Left
function()
return {
x = 0,
y = 0,
width = quarter_w,
height = canvas_h,
}
end,
-- Right, this test non-continous screen index on purpose, as this setup
-- Often requires dual-GPU, it _will_ happen.
function()
return {
x = canvas_w-quarter_w,
y = 0,
width = quarter_w,
height = canvas_h,
}
end,
-- Center
function()
return {
x = quarter_w,
y = 0,
width = half_w,
height = math.floor(canvas_h*(9/16)),
}
end,
},
-- Same as the previous one, but with the gap centered
{
-- Left
function()
return {
x = 0,
y = 0,
width = quarter_w,
height = canvas_h,
}
end,
-- Right, this test non-continous screen index on purpose, as this setup
-- Often requires dual-GPU, it _will_ happen.
function()
return {
x = canvas_w-quarter_w,
y = 0,
width = quarter_w,
height = canvas_h,
}
end,
-- Center
function()
return {
x = quarter_w,
y = math.ceil((canvas_h-(canvas_h*(9/16)))/2),
width = half_w,
height = math.floor(canvas_h*(9/16)),
}
end,
-- Keep the same screens as the last test, just move them
keep = true,
},
-- AMD Eyefinity / (old) NVIDIA MOSAIC style config (symmetric grid)
{
function()
return {
x = 0,
y = 0,
width = half_w,
height = half_h,
}
end,
function()
return {
x = half_w,
y = 0,
width = half_w,
height = half_h,
}
end,
function()
return {
x = 0,
y = half_h,
width = half_w,
height = half_h,
}
end,
function()
return {
x = half_w,
y = half_h,
width = half_w,
height = half_h,
}
end,
},
-- Corner case 1: Non-continuous screens
-- If there is nothing and client is drag&dropped into that area, some
-- geometry callbacks may break in nil index.
{
function()
return {
x = 0,
y = 0,
width = quarter_w,
height = canvas_h,
}
end,
function()
return {
x = canvas_w-quarter_w,
y = 0,
width = quarter_w,
height = canvas_h,
}
end,
},
-- Corner case 2: Nothing at 0x0.
-- As some position may fallback to 0x0 this need to be tested often. It
-- also caused issues such as #154
{
function()
return {
x = 0,
y = half_w,
width = half_w,
height = half_w,
}
end,
function()
return {
x = half_w,
y = 0,
width = half_w,
height = canvas_h,
}
end
},
-- Corner case 3: Many very small screens.
-- On the embedded side of the compuverse, it is possible
-- to buy 32x32 RGB OLED screens. They are usually used to display single
-- status icons, but why not use them as a desktop! This is a critical
-- market for AwesomeWM. Here's a 256px wide strip of tiny screens.
-- This may cause wibars to move to the wrong screen accidentally
{
function() return { x = 0 , y = 0, width = 32, height = 32, } end,
function() return { x = 32 , y = 0, width = 32, height = 32, } end,
function() return { x = 64 , y = 0, width = 32, height = 32, } end,
function() return { x = 96 , y = 0, width = 32, height = 32, } end,
function() return { x = 128, y = 0, width = 32, height = 32, } end,
function() return { x = 160, y = 0, width = 32, height = 32, } end,
function() return { x = 192, y = 0, width = 32, height = 32, } end,
function() return { x = 224, y = 0, width = 32, height = 32, } end,
},
-- Corner case 4: A screen taller than more than 1 other screen.
-- this may cause some issues with client resize and drag&drop move
{
function()
return {
x = 0,
y = 0,
width = half_w,
height = canvas_h,
}
end,
function()
return {
x = half_w,
y = 0,
width = half_w,
height = math.floor(canvas_h/3),
}
end,
function()
return {
x = half_w,
y = math.floor(canvas_h/3),
width = half_w,
height = math.floor(canvas_h/3),
}
end,
function()
return {
x = half_w,
y = 2*math.floor(canvas_h/3),
width = half_w,
height = math.floor(canvas_h/3),
}
end
},
-- The overlapping corner case isn't supported. There is valid use case,
-- such as a projector with a smaller resolution than the laptop screen
-- in non-scaling mirror mode, but it isn't worth the complexity it brings.
}
local function check_tag_indexes()
for s in screen do
for i, t in ipairs(s.tags) do
assert(t.index == i)
assert(t.screen == s)
end
end
end
local colors = {
"#000030",
"#300000",
"#043000",
"#302E00",
"#002C30",
"#300030",
"#301C00",
"#140030",
}
-- Paint it black
local function clear_screen()
for s in screen do
local sur = surface.widget_to_surface(
wibox.widget {
bg = "#000000",
widget = wibox.container.background
},
s.geometry.width,
s.geometry.height
)
wallpaper.fit(sur, s, "#000000")
end
end
-- Make it easier to debug the tests by showing the screen geometry when the
-- tests are executed.
local function show_screens()
wallpaper.set(color("#000000")) -- Should this clear the wallpaper? It doesn't
-- Add a wallpaper on each screen
for i=1, screen.count() do
local s = screen[i]
local w = wibox.widget {
{
text = table.concat{
"Screen: ",i,"\n",
s.geometry.width,"x",s.geometry.height,
"+",s.geometry.x,",",s.geometry.y
},
valign = "center",
align = "center",
widget = wibox.widget.textbox,
},
bg = colors[i],
fg = "#ffffff",
shape_border_color = "#ff0000",
shape_border_width = 1,
shape = shape.rectangle,
widget = wibox.container.background
}
local sur = surface.widget_to_surface(
w,
s.geometry.width,
s.geometry.height
)
wallpaper.fit(sur, s)
end
end
local function add_steps(real_steps, new_steps)
for _, dispo in ipairs(dispositions) do
-- Cleanup
table.insert(real_steps, function()
if #client.get() == 0 then return true end
for _, c in ipairs(client.get()) do
c:kill()
end
end)
table.insert(real_steps, function()
clear_screen()
local keep = dispo.keep or false
local old = {}
local geos = {}
check_tag_indexes()
if keep then
for _, sf in ipairs(dispo) do
local geo = sf and sf() or nil
-- If the function return nothing, assume the screen need to
-- be destroyed.
table.insert(geos, geo or false)
end
-- Removed screens need to be explicit.
assert(#geos >= screen.count())
-- Keep a cache to avoid working with invalid data
local old_screens = {}
for s in screen do
table.insert(old_screens, s)
end
for i, s in ipairs(old_screens) do
-- Remove the screen (if no new geometry is given
if not geos[i] then
s:fake_remove()
else
local cur_geo = s.geometry
for _, v in ipairs {"x", "y", "width", "height" } do
cur_geo[v] = geos[i][v] or cur_geo[v]
end
s:fake_resize(
cur_geo.x,
cur_geo.y,
cur_geo.width,
cur_geo.height
)
end
end
-- Add the remaining screens
for i=#old_screens + 1, #geos do
local geo = geos[i]
screen.fake_add(geo.x, geo.y, geo.width, geo.height)
end
check_tag_indexes()
else
-- Move all existing screens out of the way (to avoid temporary overlapping)
for s in screen do
s:fake_resize(canvas_w, canvas_h, 1, 1)
table.insert(old, s)
end
-- Add the new screens
for _, sf in ipairs(dispo) do
local geo = sf()
screen.fake_add(geo.x, geo.y, geo.width, geo.height)
table.insert(geos, geo)
end
-- Remove old screens
for _, s in ipairs(old) do
s:fake_remove()
end
end
show_screens()
-- Check the result is correct
local expected_count = 0
for _,v in ipairs(geos) do
expected_count = expected_count + (v and 1 or 0)
end
assert(expected_count == screen.count())
for k, geo in ipairs(geos) do
if geo then
local sgeo = screen[k].geometry
assert(geo.x == sgeo.x)
assert(geo.y == sgeo.y)
assert(geo.width == sgeo.width )
assert(geo.height == sgeo.height)
end
end
return true
end)
for _, step in ipairs(new_steps) do
table.insert(real_steps, step)
end
end
end
return setmetatable(module, {
__call = function(_,...) return add_steps(...) end
})