From 9e894d8fdd19c58bed7c39efd9fd3f8d80a58fd7 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Wed, 3 Jul 2019 10:02:28 +0300 Subject: [PATCH 1/7] test-awful-placement: Make the test repeatable In order to test the behavior of awful.placement.no_overlap with unselected tags, the test sequence must be able to run multiple times. Fix the test code to make this possible (currently it just performs the same sequence 3 times, the code to actually test the behavior with different tags will be added later). Indentation is unchanged to make the changes obvious in diff; the next commit will contain formatting changes without anything else. Signed-off-by: Sergey Vlasov --- tests/test-awful-placement.lua | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index be1a49c26..c20a6c1b6 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -44,14 +44,15 @@ local function default_test(c, geometry) return true end -local client_count = 0 local client_data = {} local function add_client(args) - client_count = client_count + 1 - local client_index = client_count + local data = {} + table.insert(client_data, data) + local client_index = #client_data table.insert(tests, function(count) local name = string.format("client%010d", client_index) if count <= 1 then + data.prev_client_count = #client.get() local geometry = args.geometry(mouse.screen.workarea) test_client(class, name, nil, nil, nil, { size = { @@ -59,10 +60,9 @@ local function add_client(args) height = geometry.height } }) - client_data[client_index] = { geometry = geometry } + data.geometry = geometry return nil - elseif #client.get() >= client_index then - local data = client_data[client_index] + elseif #client.get() > data.prev_client_count then local c = data.c if not c then c = client.get()[1] @@ -75,6 +75,9 @@ local function add_client(args) end) end +-- Repeat testing 3 times. +for _, _ in ipairs{1, 2, 3} do + -- The first 100x100 window should be placed at the top left corner. add_client { geometry = function(wa) @@ -165,6 +168,23 @@ add_client { end } +-- Kill test clients to prepare for the next iteration. +table.insert(tests, function(count) + if count <= 1 then + for _, data in ipairs(client_data) do + if data.c then + data.c:kill() + data.c = nil + end + end + end + if #client.get() == 0 then + return true + end +end) + +end + runner.run_steps(tests) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From 1c27f9d2274f475fa71fe458dc011234f7fc0681 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Wed, 3 Jul 2019 10:45:06 +0300 Subject: [PATCH 2/7] test-awful-placement: Reindent after the previous change Only whitespace changes and reformatting of comments. Signed-off-by: Sergey Vlasov --- tests/test-awful-placement.lua | 191 +++++++++++++++++---------------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index c20a6c1b6..50b18d343 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -78,110 +78,111 @@ end -- Repeat testing 3 times. for _, _ in ipairs{1, 2, 3} do --- The first 100x100 window should be placed at the top left corner. -add_client { - geometry = function(wa) - return { - width = 100, - height = 100, - expected_x = wa.x, - expected_y = wa.y - } - end -} + -- The first 100x100 window should be placed at the top left corner. + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x, + expected_y = wa.y + } + end + } --- The second 100x100 window should be placed to the right of the first window. --- Note that this assumption fails if the screen is in the portrait orientation --- (e.g., the test succeeds with a 600x703 screen and fails with 600x704). -add_client { - geometry = function(wa) - return { - width = 100, - height = 100, - expected_x = wa.x + 100 + 2*border_width, - expected_y = wa.y - } - end -} + -- The second 100x100 window should be placed to the right of the first + -- window. Note that this assumption fails if the screen is in the portrait + -- orientation (e.g., the test succeeds with a 600x703 screen and fails with + -- 600x704). + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x + 100 + 2*border_width, + expected_y = wa.y + } + end + } --- The wide window should be placed below the two 100x100 windows. -add_client { - geometry = function(wa) - return { - width = wa.width - 50, - height = 100, - expected_x = wa.x, - expected_y = wa.y + tb_height + 2*border_width + 100 - } - end -} + -- The wide window should be placed below the two 100x100 windows. + add_client { + geometry = function(wa) + return { + width = wa.width - 50, + height = 100, + expected_x = wa.x, + expected_y = wa.y + tb_height + 2*border_width + 100 + } + end + } --- The first large window which does not completely fit in any free area should --- be placed at the bottom left corner (no_overlap should place it below the --- wide window, and then no_offscreen should shift it up so that it would be --- completely inside the workarea). -add_client { - geometry = function(wa) - return { - width = wa.width - 10, - height = wa.height - 50, - expected_x = wa.x, - expected_y = wa.y + 50 - 2*border_width - tb_height - } - end -} + -- The first large window which does not completely fit in any free area + -- should be placed at the bottom left corner (no_overlap should place it + -- below the wide window, and then no_offscreen should shift it up so that + -- it would be completely inside the workarea). + add_client { + geometry = function(wa) + return { + width = wa.width - 10, + height = wa.height - 50, + expected_x = wa.x, + expected_y = wa.y + 50 - 2*border_width - tb_height + } + end + } --- The second large window should be placed at the top right corner. -add_client { - geometry = function(wa) - return { - width = wa.width - 10, - height = wa.height - 50, - expected_x = wa.x + 10 - 2*border_width, - expected_y = wa.y - } - end -} + -- The second large window should be placed at the top right corner. + add_client { + geometry = function(wa) + return { + width = wa.width - 10, + height = wa.height - 50, + expected_x = wa.x + 10 - 2*border_width, + expected_y = wa.y + } + end + } --- The third large window should be placed at the bottom right corner. -add_client { - geometry = function(wa) - return { - width = wa.width - 10, - height = wa.height - 50, - expected_x = wa.x + 10 - 2*border_width, - expected_y = wa.y + 50 - 2*border_width - tb_height - } - end -} + -- The third large window should be placed at the bottom right corner. + add_client { + geometry = function(wa) + return { + width = wa.width - 10, + height = wa.height - 50, + expected_x = wa.x + 10 - 2*border_width, + expected_y = wa.y + 50 - 2*border_width - tb_height + } + end + } --- The fourth large window should be placed at the top left corner (the whole --- workarea is occupied now). -add_client { - geometry = function(wa) - return { - width = wa.width - 50, - height = wa.height - 50, - expected_x = wa.x, - expected_y = wa.y - } - end -} + -- The fourth large window should be placed at the top left corner (the + -- whole workarea is occupied now). + add_client { + geometry = function(wa) + return { + width = wa.width - 50, + height = wa.height - 50, + expected_x = wa.x, + expected_y = wa.y + } + end + } --- Kill test clients to prepare for the next iteration. -table.insert(tests, function(count) - if count <= 1 then - for _, data in ipairs(client_data) do - if data.c then - data.c:kill() - data.c = nil + -- Kill test clients to prepare for the next iteration. + table.insert(tests, function(count) + if count <= 1 then + for _, data in ipairs(client_data) do + if data.c then + data.c:kill() + data.c = nil + end end end - end - if #client.get() == 0 then - return true - end -end) + if #client.get() == 0 then + return true + end + end) end From b6c7e6751a84122c03d474faa3fc826db73520cb Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Wed, 3 Jul 2019 10:02:28 +0300 Subject: [PATCH 3/7] test-awful-placement: Test no_overlap with hidden and minimized clients Clients which are hidden or minimized should be ignored by awful.placement.no_overlap; add a test to verify this behavior. Signed-off-by: Sergey Vlasov --- tests/test-awful-placement.lua | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index 50b18d343..141c86395 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -105,6 +105,50 @@ for _, _ in ipairs{1, 2, 3} do end } + -- Hide last client. + do + local data = client_data[#client_data] + table.insert(tests, function() + data.c.hidden = true + return true + end) + end + + -- Another 100x100 client should be placed to the right of the first client + -- (the hidden client should be ignored during placement). + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x + 100 + 2*border_width, + expected_y = wa.y + } + end + } + + -- Minimize last client. + do + local data = client_data[#client_data] + table.insert(tests, function() + data.c.minimized = true + return true + end) + end + + -- Another 100x100 client should be placed to the right of the first client + -- (the minimized client should be ignored during placement). + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x + 100 + 2*border_width, + expected_y = wa.y + } + end + } + -- The wide window should be placed below the two 100x100 windows. add_client { geometry = function(wa) From ec8edaf9d5ebef55c38dbfd8244e6a9f9e88c710 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Thu, 4 Jul 2019 10:06:27 +0300 Subject: [PATCH 4/7] test-awful-placement: Test no_overlap with sticky clients Clients which are sticky should be taken into account by awful.placement.no_overlap even if they seem to be on a different tag; add a test to verify this behavior. Signed-off-by: Sergey Vlasov --- tests/test-awful-placement.lua | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index 141c86395..a3cb78398 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -90,6 +90,9 @@ for _, _ in ipairs{1, 2, 3} do end } + -- Remember the first client data for the current iteration. + local first_client_data = client_data[#client_data] + -- The second 100x100 window should be placed to the right of the first -- window. Note that this assumption fails if the screen is in the portrait -- orientation (e.g., the test succeeds with a 600x703 screen and fails with @@ -149,6 +152,54 @@ for _, _ in ipairs{1, 2, 3} do end } + -- Hide last client, and make the first client sticky. + do + local data = client_data[#client_data] + table.insert(tests, function() + data.c.hidden = true + first_client_data.c.sticky = true + return true + end) + end + + -- Another 100x100 client should be placed to the right of the first client + -- (the sticky client should be taken into account during placement). + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x + 100 + 2*border_width, + expected_y = wa.y + } + end + } + + -- Hide last client, and put the first client on the tag 9 (because the + -- first client is sticky, it should remain visible). + do + local data = client_data[#client_data] + table.insert(tests, function() + data.c.hidden = true + first_client_data.c:tags{ root.tags()[9] } + return true + end) + end + + -- Another 100x100 client should be placed to the right of the first client + -- (the sticky client should be taken into account during placement even if + -- that client seems to be on an unselected tag). + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x + 100 + 2*border_width, + expected_y = wa.y + } + end + } + -- The wide window should be placed below the two 100x100 windows. add_client { geometry = function(wa) From c48d2e5a70449267305a3f562175a14bc82e0536 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Thu, 4 Jul 2019 16:08:37 +0300 Subject: [PATCH 5/7] test-awful-placement: s/window/client/g Use the "client" term consistently instead of using "client" in code and "window" in comments. Signed-off-by: Sergey Vlasov --- tests/test-awful-placement.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index a3cb78398..fd2f3d0e5 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -78,7 +78,7 @@ end -- Repeat testing 3 times. for _, _ in ipairs{1, 2, 3} do - -- The first 100x100 window should be placed at the top left corner. + -- The first 100x100 client should be placed at the top left corner. add_client { geometry = function(wa) return { @@ -93,8 +93,8 @@ for _, _ in ipairs{1, 2, 3} do -- Remember the first client data for the current iteration. local first_client_data = client_data[#client_data] - -- The second 100x100 window should be placed to the right of the first - -- window. Note that this assumption fails if the screen is in the portrait + -- The second 100x100 client should be placed to the right of the first + -- client. Note that this assumption fails if the screen is in the portrait -- orientation (e.g., the test succeeds with a 600x703 screen and fails with -- 600x704). add_client { @@ -200,7 +200,7 @@ for _, _ in ipairs{1, 2, 3} do end } - -- The wide window should be placed below the two 100x100 windows. + -- The wide client should be placed below the two 100x100 client. add_client { geometry = function(wa) return { @@ -212,9 +212,9 @@ for _, _ in ipairs{1, 2, 3} do end } - -- The first large window which does not completely fit in any free area + -- The first large client which does not completely fit in any free area -- should be placed at the bottom left corner (no_overlap should place it - -- below the wide window, and then no_offscreen should shift it up so that + -- below the wide client, and then no_offscreen should shift it up so that -- it would be completely inside the workarea). add_client { geometry = function(wa) @@ -227,7 +227,7 @@ for _, _ in ipairs{1, 2, 3} do end } - -- The second large window should be placed at the top right corner. + -- The second large client should be placed at the top right corner. add_client { geometry = function(wa) return { @@ -239,7 +239,7 @@ for _, _ in ipairs{1, 2, 3} do end } - -- The third large window should be placed at the bottom right corner. + -- The third large client should be placed at the bottom right corner. add_client { geometry = function(wa) return { @@ -251,7 +251,7 @@ for _, _ in ipairs{1, 2, 3} do end } - -- The fourth large window should be placed at the top left corner (the + -- The fourth large client should be placed at the top left corner (the -- whole workarea is occupied now). add_client { geometry = function(wa) From 93c4f369cf12ddc09e8b4de5b78f8370afadd457 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Thu, 4 Jul 2019 11:25:17 +0300 Subject: [PATCH 6/7] awful.placement: Fix no_overlap with unselected tags (#2809) The awful.placement.no_overlap function always looked at the currently visible clients when placing a new client. This produced a confusing result when using awful.rules or the sn_rules argument of awful.spawn to place the client on an unselected tag (the client was placed as if it would be placed on a currently selected tag; if multiple clients were placed on the same unselected tag, in many cases they were placed at the same position, overlapping each other). Make awful.placement.no_overlap check tags of the placed client and handle the case of placement on an unselected tag in a more useful way: - If the client is sticky or at least one of the client tags is selected, keep the previous behavior: avoid overlap with all other floating clients which are currently visible, and use the currently active layout to determine the floating status. An explicit check based on `c:tags()` is made instead of using `c:isvisible()`, so that the previous behavior is kept even if the client is hidden or minimized for some reason. - If all client tags are unselected, avoid overlap with all other floating clients which either are sticky or share at least one tag with the placed client, and use the layout of the first tag of the placed client to determine the floating status. Signed-off-by: Sergey Vlasov --- lib/awful/placement.lua | 51 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua index e79c9ed22..173ce7bea 100644 --- a/lib/awful/placement.lua +++ b/lib/awful/placement.lua @@ -894,6 +894,39 @@ function placement.no_offscreen(c, args) return fix_new_geometry(geometry, args, true) end +-- Check whether the client is on at least one of selected tags (similar to +-- `c:isvisible()`, but without checks for the hidden or minimized state). +local function client_on_selected_tags(c) + if c.sticky then + return true + else + for _, t in pairs(c:tags()) do + if t.selected then + return true + end + end + return false + end +end + +-- Check whether the client would be visible if the specified set of tags is +-- selected (using the same set of conditions as `c:isvisible()`, but checking +-- against the specified tags instead of currently selected ones). +local function client_visible_on_tags(c, tags) + if c.hidden or c.minimized then + return false + elseif c.sticky then + return true + else + for _, t in pairs(c:tags()) do + if gtable.hasitem(tags, t) then + return true + end + end + return false + end +end + --- Place the client where there's place available with minimum overlap. --@DOC_awful_placement_no_overlap_EXAMPLE@ -- @param c The client. @@ -905,8 +938,22 @@ function placement.no_overlap(c, args) args = add_context(args, "no_overlap") local geometry = geometry_common(c, args) local screen = get_screen(c.screen or a_screen.getbycoord(geometry.x, geometry.y)) - local cls = client.visible(screen) - local curlay = layout.get() + local cls, curlay + if client_on_selected_tags(c) then + cls = client.visible(screen) + curlay = layout.get() + else + -- When placing a client on unselected tags, place it as if all tags of + -- that client are selected. + local tags = c:tags() + cls = {} + for _, other_c in pairs(capi.client.get(screen)) do + if client_visible_on_tags(other_c, tags) then + table.insert(cls, other_c) + end + end + curlay = tags[1] and tags[1].layout + end local areas = { screen.workarea } for _, cl in pairs(cls) do if cl ~= c From 51e823832c4763aa6e478467d91c0c7b8ed546e5 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Thu, 4 Jul 2019 11:34:51 +0300 Subject: [PATCH 7/7] test-awful-placement: Test no_overlap with unselected tags (#2809) Test the behavior of awful.placement.no_overlap when placing clients on unselected tags. Currently this tests only the most common case with only a single selected tag and a single tag set for each client. Signed-off-by: Sergey Vlasov --- tests/test-awful-placement.lua | 45 +++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index fd2f3d0e5..7761b3798 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -54,7 +54,7 @@ local function add_client(args) if count <= 1 then data.prev_client_count = #client.get() local geometry = args.geometry(mouse.screen.workarea) - test_client(class, name, nil, nil, nil, { + test_client(class, name, args.sn_rules, nil, nil, { size = { width = geometry.width, height = geometry.height @@ -75,11 +75,40 @@ local function add_client(args) end) end --- Repeat testing 3 times. -for _, _ in ipairs{1, 2, 3} do +-- Repeat testing 3 times, placing clients on different tags: +-- +-- - Iteration 1 places clients on the tag 1, which is selected. +-- +-- - Iteration 2 places clients on the tag 2, which is unselected; the +-- selected tag 1 remains empty. +-- +-- - Iteration 3 places clients on the tag 3, which is unselected; the +-- selected tag 1 contains some clients. +-- +for _, tag_num in ipairs{1, 2, 3} do + + local sn_rules + if tag_num > 1 then + sn_rules = { tag = root.tags()[tag_num] } + end + + -- Put a 100x100 client on the tag 1 before iteration 3. + if tag_num == 3 then + add_client { + geometry = function(wa) + return { + width = 100, + height = 100, + expected_x = wa.x, + expected_y = wa.y + } + end + } + end -- The first 100x100 client should be placed at the top left corner. add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = 100, @@ -98,6 +127,7 @@ for _, _ in ipairs{1, 2, 3} do -- orientation (e.g., the test succeeds with a 600x703 screen and fails with -- 600x704). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = 100, @@ -120,6 +150,7 @@ for _, _ in ipairs{1, 2, 3} do -- Another 100x100 client should be placed to the right of the first client -- (the hidden client should be ignored during placement). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = 100, @@ -142,6 +173,7 @@ for _, _ in ipairs{1, 2, 3} do -- Another 100x100 client should be placed to the right of the first client -- (the minimized client should be ignored during placement). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = 100, @@ -165,6 +197,7 @@ for _, _ in ipairs{1, 2, 3} do -- Another 100x100 client should be placed to the right of the first client -- (the sticky client should be taken into account during placement). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = 100, @@ -190,6 +223,7 @@ for _, _ in ipairs{1, 2, 3} do -- (the sticky client should be taken into account during placement even if -- that client seems to be on an unselected tag). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = 100, @@ -202,6 +236,7 @@ for _, _ in ipairs{1, 2, 3} do -- The wide client should be placed below the two 100x100 client. add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = wa.width - 50, @@ -217,6 +252,7 @@ for _, _ in ipairs{1, 2, 3} do -- below the wide client, and then no_offscreen should shift it up so that -- it would be completely inside the workarea). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = wa.width - 10, @@ -229,6 +265,7 @@ for _, _ in ipairs{1, 2, 3} do -- The second large client should be placed at the top right corner. add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = wa.width - 10, @@ -241,6 +278,7 @@ for _, _ in ipairs{1, 2, 3} do -- The third large client should be placed at the bottom right corner. add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = wa.width - 10, @@ -254,6 +292,7 @@ for _, _ in ipairs{1, 2, 3} do -- The fourth large client should be placed at the top left corner (the -- whole workarea is occupied now). add_client { + sn_rules = sn_rules, geometry = function(wa) return { width = wa.width - 50,