Repository: qtile/qtile

Issue:
Turning screen off and on again creates some issues
# Issue description

I have observed an **issue with my systray and the assigment of groups to specific screens after turning off all my screens and then turning one or more back on**. I don't know whether this issue only shows up when using multiple screens.

*A bit more detail:*
After Qtile starts up, I assign a specific group to each screen through a function in my config. This works fine. After turning screens off and on, however, I end up with the standard groups being selected - i.e. group 1 for screen 1, group 2 for screen 2, etc.
I have set up a fake screen, so I am using `qtile.config.fake_screens` for my setup, not `qtile.config.screens`. I have not tested the other variable so far.

On top of that, the layout of my systray icons is broken after I turn on the screens again. I have observed this before when testing #1899 after restarting Qtile. @m-col : Do you think the issues could be related to that PR? (I have nothing tied to the `screen_change`-hook.)

# Qtile version

Most recent master.

# Stack traces

There actually is a bit of a stacktrace that might be related to this:

```log
2021-03-21 08:16:29,679 ERROR libqtile core.py:_xpoll():L313 Got an exception in poll loop
Traceback (most recent call last):
  File "/home/tcl/Software/qtile/libqtile/backend/x11/core.py", line 281, in _xpoll
    ret = target(event)
  File "/home/tcl/Software/qtile/libqtile/widget/systray.py", line 88, in handle_DestroyNotify
    del(self.systray.icons[wid])
KeyError: 23068737
2021-03-21 08:16:30,406 ERROR libqtile core.py:_xpoll():L313 Got an exception in poll loop
Traceback (most recent call last):
  File "/home/tcl/Software/qtile/libqtile/backend/x11/core.py", line 281, in _xpoll
    ret = target(event)
  File "/home/tcl/Software/qtile/libqtile/widget/systray.py", line 88, in handle_DestroyNotify
    del(self.systray.icons[wid])
KeyError: 23068745
```

# Configuration

```python
@hook.subscribe.screen_change
def restart_on_add_screen(ev):
    # reload_screen()
    # libqtile.qtile.cmd_restart()
    pass

fake_screens = init_fakescreens()
```


Discussion:
This is what the systray looks like (ignore the colors):
![image](https://user-images.githubusercontent.com/12257447/111897624-4ae4e200-8a21-11eb-8ecc-43fdbed05d30.png)

This is what it should normally look like:
![screenshot_2021-03-21_08-39-19_136x29](https://user-images.githubusercontent.com/12257447/111897640-7c5dad80-8a21-11eb-8c67-b33f16c80801.png)

The issues also occur when I only turn off/on one of my screens.

NOTE: I just realized that the first two icons in the "systray" aren't actually parf of the systray; these are a `KeyboardLayout`- and a `Volume`-widget. They seem to be moved behind the systray icons.
Is reproduction of the issue completely reliable? If it is can you check before and after #1899 to confirm that it introduced the problem?
Also when you turn on and off screens is this changing randr configuration (i.e. via autorandr or similar)?
Reproduction is reliable. I'll go back and compare the situation.

I have no configuration for autorandr prepared, but since it is installed it might be doing something without me knowing. I'll try to uninstall it, too.
I checked the commits, and it is definitely https://github.com/qtile/qtile/commit/9e6af506cd714fc993c5b7f534c689b80cc21793 that introduces the issues.

Everything also happens after uninstalling `autorandr`.

One more thing I noticed: It seems I have to turn off and on the screen containing the systray (and the one actual fake screen) for the issues to be triggered. Turning off/on my other screen doesn't trigger any issues.
I'm not on master but on v0.17.0 and I also stumble on those systray KeyError exceptions after resizing the Qtile screen when I change resolution with xrandr and quit the app in systray.
 
I'm doing the following in `screen_change` event subscribe:
```
@hook.subscribe.screen_change
def restart_on_randr(event):
    for screen in qtile.screens:
        screen.resize(w=event.width, h=event.height)
```
I'm doing this mostly because when I change resolution, Qtile does not follow and does not update my screen accordingly (see here the bottom bar in the middle of the screen)
![image](https://user-images.githubusercontent.com/121836/112754594-0cc95e80-8fdd-11eb-941e-384277bcd851.png)

I'm also seeing the painting of systray icons in random places like here with the slack icon:
![image](https://user-images.githubusercontent.com/121836/112754670-6f225f00-8fdd-11eb-83b0-b32b9f19c348.png)

And the KeyError exception occurs when I quit the slack application:
```
Traceback (most recent call last):
  File "qtile/libqtile/backend/x11/core.py", line 273, in _xpoll
    ret = target(event)
  File "qtile/libqtile/widget/systray.py", line 88, in handle_DestroyNotify
    del(self.systray.icons[wid])
KeyError: 54525979
```
I think the issue is global to every systray icons since I've also the issue with Steam systray icon.
Do you have `reconfigure_screens` set to True in your config?

Resolving patch:
diff --git a/libqtile/bar.py b/libqtile/bar.py
--- a/libqtile/bar.py
+++ b/libqtile/bar.py
@@ -250,6 +250,7 @@ def _configure_widget(self, widget):
         configured = True
         try:
             widget._configure(self.qtile, self)
+            widget.configured = True
         except Exception as e:
             logger.error(
                 "{} widget crashed during _configure with "
diff --git a/libqtile/widget/base.py b/libqtile/widget/base.py
--- a/libqtile/widget/base.py
+++ b/libqtile/widget/base.py
@@ -201,7 +201,6 @@ def _configure(self, qtile, bar):
             self.bar.height
         )
         if not self.configured:
-            self.configured = True
             self.qtile.call_soon(self.timer_setup)
             self.qtile.call_soon(asyncio.create_task, self._config_async())
 
diff --git a/libqtile/widget/systray.py b/libqtile/widget/systray.py
--- a/libqtile/widget/systray.py
+++ b/libqtile/widget/systray.py
@@ -118,6 +118,10 @@ def calculate_length(self):
 
     def _configure(self, qtile, bar):
         base._Widget._configure(self, qtile, bar)
+
+        if self.configured:
+            return
+
         win = qtile.conn.create_window(-1, -1, 1, 1)
         window._Window.__init__(self, xcbq.Window(qtile.conn, win.wid), qtile)
         qtile.windows_map[win.wid] = self


Test patch: