Repository: qtile/qtile

Issue:
Bar not showing up on reload
Currently using this [config](https://github.com/ervinpopescu/dotfiles/tree/main/.config/qtile)

When I reload qtile with [`qtile.cmd_reload_config`](https://github.com/ervinpopescu/dotfiles/blob/main/.config/qtile/widgets.py#L28) or [`lazy.reload_config`](https://github.com/ervinpopescu/dotfiles/blob/main/.config/qtile/keys.py#L100), the windows reload, but the bar doesn't.

```
2022-02-23 11:09:33,846 WARNING libqtile lifecycle.py:_atexit():L33 Restarting Qtile with os.execv(...)
2022-02-23 11:09:35,154 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to image_1 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['image_1']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,155 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_1 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_1']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,156 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_2 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_2']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,158 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to clock_1 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['clock_1']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,161 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_3 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_3']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,164 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_4 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_4']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,165 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_5 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_5']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,196 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_6 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_6']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,196 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_7 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_7']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,197 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_8 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_8']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,198 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_9 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_9']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,198 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to spacer_1 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['spacer_1']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,199 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_10 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_10']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,201 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_11 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_11']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,202 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to genpolltext_1 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['genpolltext_1']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,203 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to genpolltext_2 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['genpolltext_2']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,203 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_12 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_12']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,203 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_13 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_13']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,204 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to spacer_2 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['spacer_2']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,204 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to genpolltext_3 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['genpolltext_3']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,204 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to spacer_3 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['spacer_3']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,205 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to textbox_14 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['textbox_14']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
2022-02-23 11:09:35,205 WARNING libqtile manager.py:register_widget():L571 Widget was renamed to spacer_4 in qtile.widgets_map. To bind commands, rename the widget or use lazy.widget['spacer_4']. A duplicate widget was also found in the same bar or screen. If you wish to access the widget via lazy.bar[position].widget or lazy.screen.widget, you should rename the widget in your config.
```

This is after restarting. I don't think it's related to the issue though.


Discussion:
Yeah, that change doesn't stop the bar loading (but I wonder if we should drop that logger level to INFO).

Are you able to do a git bisect?
I'm using latest git version 
```
$ qtile -v
0.20.1.dev65+g77589e7e
```
So that should be ok. It's as simple as running `git bisect` on the cloned repo?

Edit: my bad, posted before looking it up. It's not exactly the best time, I have an interview coming up in 2h, but I'll do it asap.
I did the bisect with the good revision being v0.20.0 and nothing useful came through. 
```
$ git bisect log

git bisect start
# bad: [77589e7eb2dbefb6d1a09ab2aa8794265fb499d9] WL: support libinput configs for type:touchpad
git bisect bad 77589e7eb2dbefb6d1a09ab2aa8794265fb499d9
# bad: [77589e7eb2dbefb6d1a09ab2aa8794265fb499d9] WL: support libinput configs for type:touchpad
git bisect bad 77589e7eb2dbefb6d1a09ab2aa8794265fb499d9
# good: [6bb5520c3d9168fc3d495ed902c36a304ac182aa] v0.20.0
git bisect good 6bb5520c3d9168fc3d495ed902c36a304ac182aa
# bad: [5f6ab4bcafbcb31e45dbd178a77f49eb647505a6] WL: transform internal windows and wallpapers correctly on outputs
git bisect bad 5f6ab4bcafbcb31e45dbd178a77f49eb647505a6
# bad: [7f451f901f92bdeca2ce186e3e3f3054c35e832f] Fix creation of mirrors in same Bar
git bisect bad 7f451f901f92bdeca2ce186e3e3f3054c35e832f
# bad: [7056c1f3013bedd4601456930aec35cd85f7b88c] WL: Don't die when some XWayland clients close their window
git bisect bad 7056c1f3013bedd4601456930aec35cd85f7b88c
# bad: [77bcbc3f651561c5288c269339c807b0eb29b121] Fix Systray crash when screens are reconfigured
git bisect bad 77bcbc3f651561c5288c269339c807b0eb29b121
# bad: [0ed2c76f05a705cd0d5c79ee084abe465284bf88] fix changelog errors
git bisect bad 0ed2c76f05a705cd0d5c79ee084abe465284bf88
# bad: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
git bisect bad 366017b95d796ba4923d713d4718e9e108c7dd55
# first bad commit: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
# bad: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
git bisect bad 366017b95d796ba4923d713d4718e9e108c7dd55
# first bad commit: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
# first bad commit: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
# first bad commit: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
# skip: [6bb5520c3d9168fc3d495ed902c36a304ac182aa] v0.20.0
git bisect skip 6bb5520c3d9168fc3d495ed902c36a304ac182aa
# first bad commit: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
# bad: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
git bisect bad 366017b95d796ba4923d713d4718e9e108c7dd55
# first bad commit: [366017b95d796ba4923d713d4718e9e108c7dd55] Add command to centre floating windows
```

```
$ git log 366017b9

commit 366017b95d796ba4923d713d4718e9e108c7dd55 (HEAD, refs/bisect/bad)
Author: elParaguayo <elparaguayocode@gmail.com>
Date:   Fri Dec 31 13:32:22 2021 +0000

    Add command to centre floating windows

    This is from a discussion in the qtile-dev group: add a new
    `cmd_center` to position a floating window in the middle of the
    screen.

 CHANGELOG                |  1 +
 libqtile/backend/base.py | 24 ++++++++++++++++++++++++
 test/test_window.py      | 21 +++++++++++++++++++++
 3 files changed, 46 insertions(+)

commit 6bb5520c3d9168fc3d495ed902c36a304ac182aa (tag: v0.20.0, refs/bisect/skip-6bb5520c3d9168fc3d495ed902c36a304ac182aa, refs/bisect/good-6bb5520c3d9168fc3d495ed902c36a304ac182aa)
Author: mcol <mcol@posteo.net>
Date:   Mon Jan 24 19:53:56 2022 +0000

    v0.20.0
```

Now I'm building from the AUR. I don't think the results should be any different than the `sudo pip install .` though.
That's bizarre. That's got nothing to do with the bar or widgets!

I also can't recreate it. Using my own config on latest git, `reload_config` works perfectly.

I'll try with your config later today.
> Edit: my bad, posted before looking it up. It's not exactly the best time, I have an interview coming up in 2h, but I'll do it asap.

Please, please, please focus on your interview. This can wait.

Good luck!

> Please, please, please focus on your interview. This can wait.
> Good luck!

Thanks man!! It's just a course interview though, that's why I had some time.


I haven't tested you config but your screens config is a bit strange. This line:
``` python
    lazy.spawn(
        "xrandr --output eDP-1 --auto --output HDMI-1 --auto --right-of eDP-1")
```
Won't do anything as `lazy` commands only work in keybindings and mouse callbacks. That line does not run `xrandr` it just creates an object which would tell the server to run the command but you have no way to pass that object to the server.

I'd put the xrandr script in an autostart script. 

You can also have more than one screen defined in your config. If you only have one screen connected, qtile will just use the first configuration and ignore the second.

However, I doubt this is causing your issue but I thought I'd point this out anyway!
Can you check if this issue happens with the default config?

I'm most sceptical about the `xrandr` lines in your `screens.py` but it's just a hunch. Once the bar has disappeared, can you run `qtile cmd-obj -o cmd -f reconfigure_screens` and let me know if that helps.
> Won't do anything as `lazy` commands only work in keybindings and mouse callbacks. That line does not run `xrandr` it just creates an object which would tell the server to run the command but you have no way to pass that object to the server.

Ahh, snap, didn't know that. I'm not entirely sure, but i remember it not working before I added that line xD. I'll check now commenting those lines to see if that's the problem and in the morning I'll check the screen setup to see if what you said is true. xD "Always fact check yourself and other people" - I live by this.

>Once the bar has disappeared, can you run `qtile cmd-obj -o cmd -f reconfigure_screens` and let me know if that helps.

Did that already. It did nothing. Forgot to mention it xD. 
Because of this, I'm skeptical about the screens thing.

Sooo, I was right :<

Widgets don't seem to be the problem either.
The things I autostart no(thought about picom but apparently not).
Idk what to check now. 
Did you try the default config? Might be worth copying the screen so you can have two (if you're using multiple monitors)
I did try the default and it works perfectly.
Ok - so there is something in your config that's triggering the issue.

What you could try is starting with the default config and gradually replace parts with bits from your own config until you find which bit causes the break. At that point we can then see if this is a qtile bug or whether it's an error in your config.
Already on it. Will try to demodularize it too to see if that is the issue.
The widgets seem to be the issue... But I can't exactly figure out why. If I import them from another module, the bar stops redrawing. As in, it remains static. Before, it disappeared.
Not sure I entirely understand what you're saying. I thought the bar was disappearing before, when you were importing widgets from a different file. Your post above seems to say the opposite.

Also, can you reproduce the issue with minimal changes to the default config so I can test?
I changed some stuff now.
I defined functions that return the widgets I wanted.
```
def common_widgets():
    return [...]
def widgets_top_screen1():
    return [...]
def widgets_top_screen2():
    return [...]
```
Keeping the widgets elsewhere seems to be ok now, as long as I keep the `screens` in the `config.py`.
I'm going to try the same approach with the `screens`.

```
from libqtile import bar
from libqtile.config import Screen

from widgets import (
    colors_nord,
    common_widgets,
    widgets_top_screen1,
    widgets_top_screen2,
)

widgets1 = widgets_top_screen1()
widgets2 = widgets_top_screen2()
widgets1[0:0] = common_widgets()
widgets2[0:0] = common_widgets()


def get_screens():
    return [
        Screen(
            top=bar.Bar(
                widgets1,
                25,
                margin=3,
                background=colors_nord[0]
            ),
        ),
        Screen(
            top=bar.Bar(
                widgets2,
                25,
                margin=3,
                background=colors_nord[0]
            ),
        ),
    ]

```

That doesn't work... I get this:

```
2022-02-24 20:03:13,072 ERROR libqtile manager.py:cmd_reload_config():L259 
Configuration error: cannot import name 'get_screens' from 'modules.screens'
(/home/ervin/.config/qtile/modules/screens.py)
```

> Also, can you reproduce the issue with minimal changes to the default config so I can test?

I will try this in a few minutes, after checking one more time that the config works with `screens` in it and everything else imported, for my sanity's sake xDDD 




Having a module called `screens` could be problematic as it would clash with the variable name.
Oh, that's my non-programming self doing stuff by his own again xD.

FINALLY, SOMETHING INTERESTING IN THE LOG!!
```
2022-02-24 20:40:00,713 ERROR libqtile manager.py:_finalize_configurables():L294 exception during finalize
Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/libqtile/core/manager.py", line 292, in _finalize_configurables
    gap.finalize()
  File "/usr/lib/python3.10/site-packages/libqtile/bar.py", line 371, in finalize
    self.future.cancel()
AttributeError: 'NoneType' object has no attribute 'cancel'
```
This happens if I try to reload 2 times in a row when the bar has disappeared.
This is what "disappearing" means.
![2022-02-24_20-41](https://user-images.githubusercontent.com/84532402/155587280-ef83f902-5c0f-463b-9635-76e3bf0a94b5.png)

But at some point in my quest for modularization the bar didn't disappear, it just became non-interactive.

```
$ tree ~/.config/qtile
/home/ervin/.config/qtile/
├── config.py
└── modules
    ├── get_screens.py
    ├── groups.py
    ├── __init__.py
    ├── keys.py
    ├── layouts.py
    ├── matches.py
    ├── mouse.py
    └── widgets.py
```
\_\_init\_\_ is empty.

get_screens.py
```
from libqtile import bar
from libqtile.config import Screen

from .widgets import (
    colors_nord,
    common_widgets,
    widgets_top_screen1,
    widgets_top_screen2,
)

widgets1 = widgets_top_screen1()
widgets2 = widgets_top_screen2()
widgets1[0:0] = common_widgets()
widgets2[0:0] = common_widgets()


def get_screens():
    return [
        Screen(
            top=bar.Bar(
                widgets1,
                25,
                margin=3,
                background=colors_nord[0]
            ),
        ),
        Screen(
            top=bar.Bar(
                widgets2,
                25,
                margin=3,
                background=colors_nord[0]
            ),
        ),
    ]
```
config.py
```
...
from modules.get_screens import get_screens
screens = get_screens()
...
```
Now I'll finally get to the default config. xD
I can't reproduce with the default. It's probably something I'm doing programatically wrong in Python. 

Using this [config](https://github.com/antoniosarosi/dotfiles/tree/master/.config/qtile) results in the exact same issue. It's practically the same as mine before I wrote those functions for defining sets of widgets. 

Deleting the `screens` and letting qtile draw the default bar results in the same issue also(I'm keeping the rest of the stuff, that is, layouts etc). 
> Oh, that's my non-programming self doing stuff by his own again xD.
> 
> FINALLY, SOMETHING INTERESTING IN THE LOG!!
> 
> ```
> 2022-02-24 20:40:00,713 ERROR libqtile manager.py:_finalize_configurables():L294 exception during finalize
> Traceback (most recent call last):
>   File "/usr/lib/python3.10/site-packages/libqtile/core/manager.py", line 292, in _finalize_configurables
>     gap.finalize()
>   File "/usr/lib/python3.10/site-packages/libqtile/bar.py", line 371, in finalize
>     self.future.cancel()
> AttributeError: 'NoneType' object has no attribute 'cancel'
> ```

That's weird. `self.future` would only be `None` if you're finalising the bar before it's even scheduled to be drawn for the first time. That would happen if the bar crashed during configuration but I'd expect to see an error in the log if that were the case.
I've pushed the [log](https://raw.githubusercontent.com/ervinpopescu/dotfiles/main/qtile.log) to the dotfiles repo if you want to look at all of it. 

I've also pushed the current working config.
No idea what I'm doing wrong but I can't even get that config from a few posts before to even load...
Watch out for the dependencies... Technically, the keys that take rofi and other stuff shouldn't matter, I can't think of anything else.
Yeah, it's weird. Not getting any areas but it keeps loading the default config.
Based on [this thread on Reddit](https://www.reddit.com/r/qtile/comments/t3wy4z/bar_question_qtile_is_awesome/), it looks like the issue is where a config file is split into parts. My guess is there's some issue with `importlib.reload` in that scenario.

Will do some investigating later today.
Do you need something like this?: https://github.com/m-col/qtile-config/blob/master/config.py#L28-L31
Yeah. That's what I was thinking too but can't test yet.
Confirmed - that fixes it.

Now, the question is, is there any way we can build that into our reload code? I have an idea but it's a bit hacky...

Resolving patch:
diff --git a/libqtile/confreader.py b/libqtile/confreader.py
--- a/libqtile/confreader.py
+++ b/libqtile/confreader.py
@@ -99,6 +99,20 @@ def update(self, *, fake_screens=None, **settings):
                 value = getattr(self, key, default[key])
             setattr(self, key, value)
 
+    def _reload_config_submodules(self, path: Path) -> None:
+        """Reloads python files from same folder as config file."""
+        folder = path.parent
+        for module in sys.modules.copy().values():
+
+            # Skip built-ins and anything with no filepath.
+            if hasattr(module, "__file__") and module.__file__ is not None:
+                subpath = Path(module.__file__)
+
+                # Check if the module is in the config folder or subfolder
+                # if so, reload it
+                if folder in subpath.parents:
+                    importlib.reload(module)
+
     def load(self):
         if not self.file_path:
             return
@@ -108,6 +122,7 @@ def load(self):
         sys.path.insert(0, path.parent.as_posix())
 
         if name in sys.modules:
+            self._reload_config_submodules(path)
             config = importlib.reload(sys.modules[name])
         else:
             config = importlib.import_module(name)


Test patch: