How does screen:swap work?
Hello,
is there any good documentation available on how screen:swap works? I couldn't find any that (be it official docs, blogs, Redit, stackoverflow, etc) could explain the the actual behavior I am experiencing.
I am on Ubuntu 20.04 running Awesome v4.3.
$ awesome -v
awesome v4.3 (Too long)
• Compiled against Lua 5.3.3 (running with Lua 5.3)
• D-Bus support: ✔
• execinfo support: ✔
• xcb-randr version: 1.6
• LGI version: 0.9.2
I configured some keybindings to swap screens but it wouldn't do anything in that regard. My debug output led me to the conclusion that I was doing the right thing (i.e. computing the correct screen indices) but any call to screen[a]:swap(screen[b]) doesn't do anything.
I then managed to swap my screens with a statically defined call (e.g. screen[2]:swap(screen[1])). However there are two caveats here
- It only works when I call the code before the call to
awful.screen.connect_for_each_screen. When I insert the same code after this code block it won't swap anything. - When I want to swap the screens back one might expect to call something like
screen[2]:swap(screen[1])again orscreen[1]:swap(screen[2]). However, I need to callscreen[1]:swap(screen[1])which, in my opinion, doesn't make any sense. If it does to anybody, I am more than willing to learn why
Long story short, how does swapping screens work?
If this is the wrong place for my question (at least there's a question label) I apologize.
Thanks for your hard work to make Awesome ... well ... exactly that!
Looking at the screen:swap function (in screen.c) the screens, which are stored in an array, are switched in their index.
Meaning that screen[1]:swap(screen[2])) would cause "screen[1]" to be in index two, and "screen[2]" to be in index 1.
So it shouldn't matter what indices you use, as long as they are the same ones from the call before (e.g. calling screen[1]:swap(screen[2]) twice would swap between the screens twice, thus returning them to the original layout).
If you have screens of different size you can see that they indeed change their index using this code
awful.key({ modkey, "Shift" }, "s", function ()
local s1, s2 = nil, nil
print("Swapping screens")
for s in screen do
if not s1 then
s1 = s
elseif not s2 then
s2 = s
else
break
end
end
if not s1 or not s2 then
print("Didn't find two screens to swap")
end
print("Swapping between", s1.name, "with geo", s1.geometry.width .. "x" .. s1.geometry.height)
print("and", s2.name, "with geo", s2.geometry.width .. "x" .. s2.geometry.height)
s1:swap(s2)
end,
{description = "Swap between screens", group = "nahh"}),
Why don't the screen's content is swapped as well ?
hmm I'm not sure. After the screen indices are swapped the list signal is emitted causing layout.arrange in lib/awful/layout/init.lua to run. I'm still trying to understand what this function does (will update).
There might be something broken here (maybe might be the expected behavior)
Hi, thanks for your quick reply. I had a look at screen.c too and came to the same conclusion regarding the index swap (of course I couldn't fully understand what, e.g. all the luaA_* calls do in detail).
I implemented several versions of my swap binding, the current one looks like this:
function (c)
local focused_screen = awful.screen.focused()
local s = focused_screen.get_next_in_direction(focused_screen, "right")
if s then
naughty.notify({
preset = naughty.config.presets.critical,
title = "Debug",
text = "Target screen: " .. tostring(s.index) .. "\n" .. "Focused screen: " .. focused_screen.index,
screen=focused_screen}
)
screen[focused_screen.index]:swap(screen[s.index])
end
end
It looks a bit dirty because I was playing around and testing so much that I wasn't in the mood...
However, when I observe the notification the indices indeed are swapped.
Also, I tested the swapped signal which is also emitted.
It's a bit of a puzzle :) As I described earlier, I can swap the screens as long as I define it statically before the call to awful.screen.connect_for_each_screen. Once that is called, swapping doesn't work anymore.
After the screen indices are swapped the list signal is emitted causing layout.arrange in lib/awful/layout/init.lua to run.
Okay thanks for the hint, maybe I can do a workaround by emitting the list signal or calling layout.arrange by hand.
Maybe it's a bug in the version provided by Ubuntu 20.04.
The screen:swap function emits another signal called swapped to which you can connect and do the swapping yourself.
See #1382 code snippet at the end, it switches all clients from one screen to another.
You could implement the same in for swapped signal as well. Something like this
screen.connect_signal("request::desktop_decoration", function(s)
s:connect_signal("swapped", function(self, other_s, was_op_on_self)
-- swapped is called twice for each screen as 'self'. If
-- 'was_op_on_self' is true that 'self' was the screen on the left
-- side of the expression s1:swap(s2)
if not was_op_on_self then
return
end
for _, t in ipairs(self.tags) do
local fallback_tag = awful.tag.find_by_name(other_s, t.name)
local self_clients = t:clients()
local other_clients
-- If we don't have a tag by the same name, we'll just "throw" the
-- client to the first tag on the other screen
if not fallback_tag then
fallback_tag = other_s.tags[1]
other_clients = {}
else
other_clients = fallback_tag:clients()
end
for _, c in ipairs(self_clients) do
c:move_to_tag(fallback_tag)
end
for _, c in ipairs(other_clients) do
c:move_to_tag(t)
end
end
end)
end)
This is a very dirty solution and is not very robust imo in case the screens are of different dimensions (seem to work if the client is maximized when the swapping happens).
The fact that it works when doing it statically before screen arrange tells that awesome doesn't "re-arrange" the clients in their new screen after the swapping. Again, I'm not sure what is the correct behavior. I'll update as soon as I find out what was expected to happen.
This is unlikely imo that it is a distribution related bug. I did the testing myself on Ubuntu 18.04 and it has the same issue. I'll test the behavior on Arch as well later (though I doubt it would change anything)
After the screen indices are swapped the list signal is emitted causing layout.arrange in lib/awful/layout/init.lua to run. I'm still trying to understand what this function does (will update).
Layout.arrange is basically the function that places clients on the screen depending on the selected client layout (tile, spirale,...).
I started to write documentation about this a while back... It's not finished but you can maybe find some information there https://github.com/awesomeWM/awesome/pull/2944
Sorry for not helping more here. I sadly never used Awesome with a multiscreen setup so I'm not aware of the intended behavior...
@ShayAgros Please be careful in your digging, tho... The screen API was refactored by elv so the current base code can be different from what there is on 4.3 stable...
@Aire-One yup sorry about that, forgot that I'm using development branch on my setup.
tested the code on stable version 4.3 and it works with the exception that the snippet should be put inside
awful.screen.connect_for_each_screen(function(s)
rather than inside
screen.connect_signal("request::desktop_decoration", function(s)
@mschloesser did more digging into this function.
Seems like list doesn't move the clients into a new screen, and just updates their geometry in the screen (which looks redundant since their screen doesn't change).
After going to the commit message that added this function I found that it leads to issue #1122. To sum it up, many functions in awesome "assume" that the screen in index 1 is left/below the screen in index 2 which is left/below the screen in index 3 (this seems more intuitive this way), however the indices are chosen based on their discovery order by Xrandr (and the indices can change when any randr event happens like screen (dis)connection or layout change).
To allow the user to make the screen indices the way he/she perceives right, this screen:swap function was introduced.
After using swap to set the screen indices the order you perceive is right, some keybinding like (from default awesome config)
awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end,
{description = "focus the next screen", group = "screen"}),
awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end,
{description = "focus the previous screen", group = "screen"}),
would change focus in a logical manner.
This is all only relevant for setups with 3+ screens though.
How did it actually change the screens when using swap at the beginning of the config file ?
Well, idk. Functions like
awful.screen.connect_for_each_screen(function(s)
or
screen.connect_signal("request::desktop_decoration", function(s)
are probably called after awesome finishes scanning all screens, so running functions like swap before these functions seems risky. The screen's coordination (x, y and width, height) are received by randr library (X system if you will) and none of it changes by calling swap, so it'd be strange if clients change their location "automagically" simply because their screens swapped indices in some array.
If you only want the clients to change screens (and don't intend to use more than two screens) you can use the code snippet I wrote (Hope this answers your question in this issue)
side note:
I'm currently going over Elv13's fake screens patchset (#2790) which might (I hope at least) allow for easier management of screens (at least a more elegant way to switch a screen's position). This might take time though
Hi, thanks for your support. I really appreciate it!
I did some more testing myself and, at least, could boil down the method call where swap seems to stop working: awful.tag (so it's not awful.screen.connect_for_each_screen). I also replaced awful.screen.connect_for_each_screen with for s in screens and removed the for loop all together, just to make sure. Also, disabled all widgets, wibar, etc. So in the end there was just as single tag definition left.
All calls before awful.tag work as I would expect it (screens swap, including clients). After calling awful.tag they won't. I also removed awful.tag completely which turned out not to be the best idea :) Without defining at least a single tag, Awesome cannot display clients at all. In case I am wrong, please let me know so I can dig deeper. My assumption was that without any manually defined tags there would be something like an implicit or a "fallback" tag that manages the clients.
In case anyone comes across this in the future, this is my implementation:
awful.screen.connect_for_each_screen(function(s)
-- [...]
s:connect_signal("swapped", function(self, other, is_source)
if not is_source then return end
local st = self.selected_tag
local sc = st:clients() -- NOTE: this is only here for convinience
local ot = other.selected_tag
local oc = ot:clients() -- but this HAS to be saved in a variable because we modify the client list in the process of swapping
for _, c in ipairs(sc) do
c:move_to_tag(ot)
end
for _, c in ipairs(oc) do
c:move_to_tag(st)
end
end)
-- [...]
end)
This swaps the currently active tag with the visible tag on the other monitor.
The "other monitor" is specified by your keybind:
awful.key({ modkey, "Control" }, "o", function ()
local focused_screen = awful.screen.focused()
local s = focused_screen.get_next_in_direction(focused_screen, "right")
-- FIXME: this only makes sense for two screens
if not s then
s = focused_screen.get_next_in_direction(focused_screen, "left")
end
if not s then
naughty.notify { preset = naughty.config.presets.critical, title = "could not get other screen" }
return
end
focused_screen:swap(s)
end,
{description = "swap screens", group = "layout"})
)
Hope this helps :)
@B4rc1 If you just wanna swap the current tag, you can use something like:
local next_screen_tag = awful.screen.focused():get_next_in_direction("right").selected_tag
awful.screen.focused().selected_tag:swap(next_screen_tag)
awful.screen.focus_relative(1)
Just FYI, for anyone wanting to rotate the different screens they have got you can use something like this: direction = -1 for right and 1 for left
function rotate_screens(direction)
local current_screen = awful.screen.focused()
local initial_scren = current_screen
while (true) do
awful.screen.focus_relative(direction)
local next_screen = awful.screen.focused()
if next_screen == initial_scren then
return
end
local current_screen_tag_name = current_screen.selected_tag.name
local next_screen_tag_name = next_screen.selected_tag.name
for _, t in ipairs(current_screen.tags) do
local fallback_tag = awful.tag.find_by_name(next_screen, t.name)
local self_clients = t:clients()
local other_clients
if not fallback_tag then
-- if not available, use first tag
fallback_tag = next_screen.tags[1]
other_clients = {}
else
other_clients = fallback_tag:clients()
end
for _, c in ipairs(self_clients) do
c:move_to_tag(fallback_tag)
end
for _, c in ipairs(other_clients) do
c:move_to_tag(t)
end
end
awful.tag.find_by_name(next_screen, current_screen_tag_name):view_only()
awful.tag.find_by_name(current_screen, next_screen_tag_name):view_only()
current_screen = next_screen
end
end
@meain Please share how to integrate rotate_screens function into rc.lua. It'd be great if it can be done with a keybinding as well. I don't know lua. I tried it but couldn't figure it out.
@sathishmanohar I don't use awesome anymore, but here is what I had in my config. https://github.com/meain/dotfiles/blob/fa63ed2931687bcafb50b229f90b878b8c0380d4/awesome/.config/awesome/rc.lua#L901-L916
@meain Works perfectly! Exactly what I wanted. Thanks!