-- FIXME See commit 7901a1c6479aea local skip_wallpapers_due_to_weird_slowdowns = true -- 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() if skip_wallpapers_due_to_weird_slowdowns then return end 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() if skip_wallpapers_due_to_weird_slowdowns then wallpaper.maximized = function() end return end 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 })