Repository: qtile/qtile

Issue:
Battery widget `notify_below` cannot send notification because Eventloop has not started
<!--
Please do not ask general questions here! There are [community
contact](https://github.com/qtile/qtile#community) options for that.
-->

# Issue description

<!--
A brief discussion of what failed and how it failed. A description of
what you tried is helpful, i.e. "When I use lazy.kill() on a window I get
the following stack trace" instead of "Closing windows doesn't work".
-->

I am trying to configure a Battery widget in combination with the `notify_below` setting. I think my config is fine, but whenever I expect a notification I instead get this message in `~/.local/share/qtile/qtile.log`:
```
WARNING libqtile utils.py:send_notification():L249 Eventloop has not started. Cannot send notification.
```


# Qtile version

<!--
Please include the exact commit hash of the version of Qtile that failed.
-->

Fresh build from `qtile-git` AUR package:
```sh
% qtile --version
0.18.1.dev39+g15e9f4cf
```

# Stack traces

<!--
Please attach any stack traces found in:

* `~/.xsession-errors`
* `~/.local/share/qtile/qtile.log`
-->
<details>
<summary>
Here is the beginning of the logs from a fresh `qtile start -l DEBUG`. It gets rather spammy after a while but above message is contained twice (I have two batteries).
</summary>

```
2021-07-13 18:46:41,220 DEBUG libqtile log_utils.py:init_log():L126 Starting logging for Qtile
2021-07-13 18:46:41,224 INFO libqtile xcursors.py:_setup_xcursor_binding():L112 xcb-cursor not found, fallback to font pointer
2021-07-13 18:46:41,245 DEBUG libqtile utils.py:guess_terminal():L315 Guessing terminal: roxterm
2021-07-13 18:46:41,245 DEBUG libqtile utils.py:guess_terminal():L315 Guessing terminal: sakura
2021-07-13 18:46:41,246 DEBUG libqtile utils.py:guess_terminal():L315 Guessing terminal: hyper
2021-07-13 18:46:41,246 DEBUG libqtile utils.py:guess_terminal():L315 Guessing terminal: alacritty
2021-07-13 18:46:41,246 INFO libqtile utils.py:guess_terminal():L319 Terminal found: alacritty
2021-07-13 18:46:41,257 DEBUG libqtile clock.py:__init__():L70 Defaulting to the system local timezone.
2021-07-13 18:46:41,562 DEBUG libqtile sensors.py:_format_sensors_output():L105 [('temp1', '41.0', '°C'), ('temp2', '39.0', '°C'), ('temp3', '0.0', '°C'), ('temp4', '0.0', '°C'), ('temp5', '0.0', '°C'), ('temp6', '0.0', '°C'), ('temp7', '0.0', '°C'), ('temp8', '0.0', '°C'), ('Package id 0', '44.0', '°C'), ('Core 0', '42.0', '°C'), ('Core 1', '41.0', '°C'), ('Core 2', '42.0', '°C'), ('Core 3', '44.0', '°C'), ('temp1', '28.0', '°C'), ('temp1', '37.0', '°C'), ('Composite', '27.9', '°C'), ('Sensor 1', '27.9', '°C'), ('Sensor 2', '29.9', '°C'), ('temp1', '41.0', '°C')]
2021-07-13 18:46:41,576 DEBUG libqtile clock.py:__init__():L70 Defaulting to the system local timezone.
2021-07-13 18:46:41,578 WARNING libqtile floating.py:__init__():L114 Non-config.Match objects in float_rules are deprecated
2021-07-13 18:46:41,594 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('1',), {})
2021-07-13 18:46:41,594 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,604 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('2',), {})
2021-07-13 18:46:41,605 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,615 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('3',), {})
2021-07-13 18:46:41,617 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,619 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('4',), {})
2021-07-13 18:46:41,619 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,621 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('5',), {})
2021-07-13 18:46:41,621 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,622 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('6',), {})
2021-07-13 18:46:41,623 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,624 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('7',), {})
2021-07-13 18:46:41,624 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,625 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('8',), {})
2021-07-13 18:46:41,625 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,627 DEBUG libqtile hook.py:fire():L372 Internal event: addgroup(('9',), {})
2021-07-13 18:46:41,627 DEBUG libqtile hook.py:fire():L372 Internal event: changegroup((), {})
2021-07-13 18:46:41,630 DEBUG libqtile core.py:setup_listener():L213 Adding io watch
2021-07-13 18:46:41,633 DEBUG libqtile ipc.py:start():L252 Starting server
2021-07-13 18:46:41,634 DEBUG libqtile core.py:_xpoll():L294 SelectionNotify
2021-07-13 18:46:41,635 DEBUG libqtile core.py:_xpoll():L297 Handling: SelectionNotify
2021-07-13 18:46:41,635 DEBUG libqtile core.py:_xpoll():L294 SelectionNotify
2021-07-13 18:46:41,635 DEBUG libqtile core.py:_xpoll():L297 Handling: SelectionNotify
2021-07-13 18:46:41,636 DEBUG libqtile hook.py:fire():L372 Internal event: setgroup((), {})
2021-07-13 18:46:41,636 DEBUG libqtile hook.py:fire():L372 Internal event: focus_change((), {})
2021-07-13 18:46:41,637 DEBUG libqtile hook.py:fire():L372 Internal event: layout_change((<libqtile.layout.xmonad.MonadTall object at 0x7f9f79b2a8b0>, <group.Group ('1')>), {})
2021-07-13 18:46:41,716 DEBUG libqtile hook.py:fire():L372 Internal event: startup_once((), {})
2021-07-13 18:46:42,948 DEBUG libqtile hook.py:fire():L372 Internal event: startup((), {})
2021-07-13 18:46:42,956 DEBUG libqtile hook.py:fire():L372 Internal event: startup_complete((), {})
2021-07-13 18:46:43,100 DEBUG libqtile sensors.py:_format_sensors_output():L105 [('temp1', '41.0', '°C'), ('temp2', '40.0', '°C'), ('temp3', '0.0', '°C'), ('temp4', '0.0', '°C'), ('temp5', '0.0', '°C'), ('temp6', '0.0', '°C'), ('temp7', '0.0', '°C'), ('temp8', '0.0', '°C'), ('Package id 0', '46.0', '°C'), ('Core 0', '46.0', '°C'), ('Core 1', '44.0', '°C'), ('Core 2', '46.0', '°C'), ('Core 3', '44.0', '°C'), ('temp1', '28.0', '°C'), ('temp1', '37.5', '°C'), ('Composite', '27.9', '°C'), ('Sensor 1', '27.9', '°C'), ('Sensor 2', '30.9', '°C'), ('temp1', '41.0', '°C')]
2021-07-13 18:46:43,114 WARNING libqtile utils.py:send_notification():L249 Eventloop has not started. Cannot send notification.
2021-07-13 18:46:43,117 WARNING libqtile utils.py:send_notification():L249 Eventloop has not started. Cannot send notification.
2021-07-13 18:46:43,135 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,136 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,136 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,137 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,137 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,137 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,137 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,138 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,138 DEBUG libqtile core.py:_xpoll():L294 Expose
2021-07-13 18:46:43,138 DEBUG libqtile core.py:_xpoll():L297 Handling: Expose
2021-07-13 18:46:43,139 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,139 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,139 DEBUG libqtile core.py:_xpoll():L294 ClientMessage
2021-07-13 18:46:43,140 DEBUG libqtile core.py:_xpoll():L297 Handling: ClientMessage
2021-07-13 18:46:43,140 DEBUG libqtile core.py:_xpoll():L294 LeaveNotify
2021-07-13 18:46:43,140 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: LeaveNotify
2021-07-13 18:46:43,141 DEBUG libqtile core.py:_xpoll():L294 EnterNotify
2021-07-13 18:46:43,141 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: EnterNotify
2021-07-13 18:46:43,141 DEBUG libqtile core.py:_xpoll():L294 UnmapNotify
2021-07-13 18:46:43,141 DEBUG libqtile core.py:_xpoll():L297 Handling: UnmapNotify
2021-07-13 18:46:43,142 DEBUG libqtile core.py:_xpoll():L294 DestroyNotify
2021-07-13 18:46:43,142 DEBUG libqtile core.py:_xpoll():L297 Handling: DestroyNotify
2021-07-13 18:46:43,146 DEBUG libqtile core.py:_xpoll():L294 MapRequest
2021-07-13 18:46:43,147 DEBUG libqtile core.py:_xpoll():L297 Handling: MapRequest
2021-07-13 18:46:43,149 DEBUG libqtile hook.py:fire():L372 Internal event: client_name_updated((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,179 DEBUG libqtile hook.py:fire():L372 Internal event: net_wm_icon_change((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,180 DEBUG libqtile hook.py:fire():L372 Internal event: client_new((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,180 DEBUG libqtile hook.py:fire():L372 Internal event: group_window_add((<group.Group ('1')>, Window(name='ArchThinkPad', wid=14680078)), {})
2021-07-13 18:46:43,186 DEBUG libqtile hook.py:fire():L372 Internal event: client_focus((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,188 DEBUG libqtile hook.py:fire():L372 Internal event: client_focus((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,188 DEBUG libqtile hook.py:fire():L372 Internal event: focus_change((), {})
2021-07-13 18:46:43,190 DEBUG libqtile hook.py:fire():L372 Internal event: client_focus((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,191 DEBUG libqtile hook.py:fire():L372 Internal event: client_managed((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,191 DEBUG libqtile core.py:_xpoll():L294 ClientMessage
2021-07-13 18:46:43,192 DEBUG libqtile core.py:_xpoll():L297 Handling: ClientMessage
2021-07-13 18:46:43,192 INFO libqtile window.py:handle_ClientMessage():L1632 Focusing window
2021-07-13 18:46:43,193 DEBUG libqtile hook.py:fire():L372 Internal event: client_focus((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,194 DEBUG libqtile hook.py:fire():L372 Internal event: client_focus((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,194 DEBUG libqtile hook.py:fire():L372 Internal event: focus_change((), {})
2021-07-13 18:46:43,195 DEBUG libqtile hook.py:fire():L372 Internal event: client_focus((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,195 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,196 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,196 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,196 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,202 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,202 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,209 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,209 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,221 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,221 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,229 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,230 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,236 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,236 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,243 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,243 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,250 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,250 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,257 DEBUG libqtile core.py:_xpoll():L294 MappingNotify
2021-07-13 18:46:43,257 DEBUG libqtile core.py:_xpoll():L297 Handling: MappingNotify
2021-07-13 18:46:43,261 DEBUG libqtile core.py:_xpoll():L294 DestroyNotify
2021-07-13 18:46:43,261 DEBUG libqtile core.py:_xpoll():L297 Handling: DestroyNotify
2021-07-13 18:46:43,262 DEBUG libqtile core.py:_xpoll():L294 DestroyNotify
2021-07-13 18:46:43,262 DEBUG libqtile core.py:_xpoll():L297 Handling: DestroyNotify
2021-07-13 18:46:43,262 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,262 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,263 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,263 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,263 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,263 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: _NET_WM_DESKTOP
2021-07-13 18:46:43,263 DEBUG libqtile window.py:update_state():L646 _NET_WM_STATE: []
2021-07-13 18:46:43,264 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,264 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,264 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,264 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,265 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,265 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,265 DEBUG libqtile core.py:_xpoll():L294 LeaveNotify
2021-07-13 18:46:43,265 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: LeaveNotify
2021-07-13 18:46:43,265 DEBUG libqtile core.py:_xpoll():L294 EnterNotify
2021-07-13 18:46:43,265 DEBUG libqtile core.py:_xpoll():L297 Handling: EnterNotify
2021-07-13 18:46:43,265 DEBUG libqtile hook.py:fire():L372 Internal event: client_mouse_enter((Window(name='ArchThinkPad', wid=14680078),), {})
2021-07-13 18:46:43,266 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,266 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,266 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,266 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,266 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,266 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,267 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,267 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,267 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,267 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,267 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,267 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,267 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,268 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,268 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,268 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,268 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,268 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,268 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,268 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,269 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,269 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,269 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,269 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,269 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,269 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,269 DEBUG libqtile core.py:_xpoll():L294 ConfigureNotify
2021-07-13 18:46:43,269 INFO libqtile core.py:_get_target_chain():L373 Unhandled event: ConfigureNotify
2021-07-13 18:46:43,270 DEBUG libqtile core.py:_xpoll():L294 PropertyNotify
2021-07-13 18:46:43,270 DEBUG libqtile core.py:_xpoll():L297 Handling: PropertyNotify
2021-07-13 18:46:43,270 DEBUG libqtile window.py:handle_PropertyNotify():L1658 PropertyNotifyEvent: WM_STATE
2021-07-13 18:46:43,610 DEBUG libqtile core.py:_xpoll():L294 ClientMessage
2021-07-13 18:46:43,611 DEBUG libqtile core.py:_xpoll():L297 Handling: ClientMessage
2021-07-13 18:46:43,611 DEBUG libqtile core.py:_xpoll():L294 ClientMessage
2021-07-13 18:46:43,611 DEBUG libqtile core.py:_xpoll():L297 Handling: ClientMessage
2021-07-13 18:46:43,632 DEBUG libqtile core.py:_xpoll():L294 MapRequest
2021-07-13 18:46:43,632 DEBUG libqtile core.py:_xpoll():L297 Handling: MapRequest
2021-07-13 18:46:43,634 DEBUG libqtile hook.py:fire():L372 Internal event: client_name_updated((Window(name='qutebrowser', wid=16777261),), {})
2021-07-13 18:46:43,830 DEBUG libqtile hook.py:fire():L372 Internal event: net_wm_icon_change((Window(name='qutebrowser', wid=16777261),), {})
2021-07-13 18:46:43,835 DEBUG libqtile hook.py:fire():L372 Internal event: client_new((Window(name='qutebrowser', wid=16777261),), {})
2021-07-13 18:46:43,836 DEBUG libqtile hook.py:fire():L372 Internal event: group_window_add((<group.Group ('2')>, Window(name='qutebrowser', wid=16777261)), {})
2021-07-13 18:46:43,840 DEBUG libqtile hook.py:fire():L372 Internal event: focus_change((), {})
2021-07-13 18:46:43,841 DEBUG libqtile hook.py:fire():L372 Internal event: client_managed((Window(name='qutebrowser', wid=16777261),), {})
```
</details>

# Configuration

<!--
Please include a link or attach your configuration to the issue.
-->
https://gitlab.com/mrossinek/dotfiles/-/blob/master/qtile/.config/qtile/config.py

> Note: Compared to the above config I changed the `notify_below` level to 95 to trigger the notification right away (current battery level is way below that :wink:)



Discussion:
Thanks for submitting this. I saw the comments on IRC too.

This is very odd indeed. You can see that the `startup_complete` hook is fired before that warning message and that hook should only be fired once the eventloop is up and running. Plus, the widget should only be polling via an event on the eventloop so the loop _must_ be running.

Needs some more digging.
> Needs some more digging.

Let me know if there is any additional information I can provide :+1: 

If the loop must indeed be running, this sounds to me like it may not be registered in the `asyncio` module properly. From what I understand qtile uses its own loop and uses `asyncio.set_event_loop` to set it globally. Maybe that is going wrong somewhere?
So, if I add a `send_notification` call in my config file and set it to fire on the `startup_complete` hook then I don't get the warning message that the widget is triggering. I can also put logger messages after the notification is sent and these appear in the log before the warning message.

This should mean that the `send_notification` function does work and the eventloop is up and running before the widget tries to send a notification.

Unfortunately, that makes very little sense to me as there's no obvious reason for the calls to be treated differently.
I can confirm that `send_notification` works as expected even on my setup. I imported it from `libqtile.utils` and send a test notification during my `startup_complete` hook. That works just fine. I have no idea what might be causing the issue within the Battery widget though..
I have not gotten around to taking another look at this yet. Hopefully I will have some time in August..
Still need to do some more digging on this but I created a simple widget that checked for a `RuntimeError` when calling `asyncio.get_running_loop()` but also checked `qtile._eventloop.is_running()`. I get the `RuntimeError` but `is_running()` returns `True`. Weird...
OK. Made some progress here.

I think the issue is that qtile is spawning a number of threads and `get_running_loop()` only works if it's called in the parent thread. One way to fix this is to set an asyncio policy such that calling `asyncio.get_event_loop()` always returns the main eventloop.

I've tested this and it does work so I'll try to tidy it up in a pull request but would be good if some people can test.

Resolving patch:
diff --git a/libqtile/core/loop.py b/libqtile/core/loop.py
--- a/libqtile/core/loop.py
+++ b/libqtile/core/loop.py
@@ -1,10 +1,14 @@
+from __future__ import annotations
+
 import asyncio
 import contextlib
 import signal
-from typing import Callable, Dict, Optional
+from typing import Callable, Dict, Optional, TYPE_CHECKING
 
 from libqtile.log_utils import logger
 
+if TYPE_CHECKING:
+    from libqtile.core.manager import Qtile
 
 class LoopContext(contextlib.AbstractAsyncContextManager):
     def __init__(
@@ -59,3 +63,20 @@ def _handle_exception(
                 logger.exception(exc)
         else:
             logger.error(f'unhandled error in event loop: {context["msg"]}')
+
+
+class QtileEventLoopPolicy(asyncio.DefaultEventLoopPolicy):  # type: ignore
+    """
+    Asyncio policy to ensure the main event loop is accessible
+    even if `get_event_loop()` is called from a different thread.
+    """
+
+    def __init__(self, qtile: Qtile) -> None:
+        asyncio.DefaultEventLoopPolicy.__init__(self)
+        self.qtile = qtile
+
+    def get_event_loop(self) -> asyncio.AbstractEventLoop:
+        if isinstance(self.qtile._eventloop, asyncio.AbstractEventLoop):
+            return self.qtile._eventloop
+
+        raise RuntimeError
diff --git a/libqtile/core/manager.py b/libqtile/core/manager.py
--- a/libqtile/core/manager.py
+++ b/libqtile/core/manager.py
@@ -48,7 +48,7 @@
 from libqtile.config import ScratchPad as ScratchPadConfig
 from libqtile.config import Screen
 from libqtile.core.lifecycle import lifecycle
-from libqtile.core.loop import LoopContext
+from libqtile.core.loop import LoopContext, QtileEventLoopPolicy
 from libqtile.core.state import QtileState
 from libqtile.dgroups import DGroups
 from libqtile.extension.base import _Extension
@@ -215,6 +215,8 @@ async def async_loop(self) -> None:
         Finalizes the Qtile instance on exit.
         """
         self._eventloop = asyncio.get_running_loop()
+        # Set the event loop policy to facilitate access to main event loop
+        asyncio.set_event_loop_policy(QtileEventLoopPolicy(self))
         self._stopped_event = asyncio.Event()
         self.core.setup_listener(self)
         try:
diff --git a/libqtile/utils.py b/libqtile/utils.py
--- a/libqtile/utils.py
+++ b/libqtile/utils.py
@@ -243,7 +243,7 @@ def send_notification(title, message, urgent=False, timeout=10000, id=None):
     urgency = 2 if urgent else 1
 
     try:
-        loop = asyncio.get_running_loop()
+        loop = asyncio.get_event_loop()
     except RuntimeError:
         logger.warning("Eventloop has not started. Cannot send notification.")
     else:


Test patch: