We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
notify-send stops working when Qtile config is reload instead restart, or if widget.Notify is disabled
### The issue:

notify-send does not work anymore if Qtile config is reload, until I restart Qtile. It also will not work anymore and wait if I disable the "widget.Notify". I am not 100% sure if this is a problem caused by Qtile.

1. Whenever I reload Qtile config instead of restarting, the next run of the program `notify-send` will hang and wait. I have to Ctrl+c to end the process. When I then restart Qtile, then the program `notify-send` works as expected.
2. And i just tested to disable the widget.Notify and try again. Now The program always hangs, reload or not. `notify-send` seems not to find anything to send to. When I enable the widget, then it works like described at point 1.

Some system information:

- Your Qtile version (qtile --version): `0.21.1.dev8+gea4462b0`
- Relevant logs from ~/.local/share/qtile/qtile.log: 
`2022-04-05 21:48:41,482 ERROR libqtile notify.py:_release():L183 Could not release org.freedesktop.Notifications.`

```
OS: Manjaro Linux [x86_64]
Kernel: 5.16.14-1-MANJARO
Uptime: 1 day, 17 hours, 36 mins
Packages: 1896 (pacman)[stable], 1 (flatpak)
Shell: zsh 5.8.1
Resolution: 2560x1440 @ 144Hz
DE: KDE Plasma 5.24.3
WM: Qtile 0.21.1.dev8+gea4462b0
Theme: breeze (GruvboxColors) [Plasma], Klaus [GTK2/3/4]
Icons: breeze [Plasma], Gruvbox [GTK2/3/4]
Font: Noto Sans (12pt) [Plasma], Noto Sans (12pt) [GTK2/3/4]
Cursor: default (24px)
Terminal: kitty
CPU: Intel Xeon E3-1230 v3 (8) @ 3.7GHz
GPU: Nvidia GeForce GTX 1070
Memory: 3225MiB / 15947MiB (20%)
Disk (/): 98GB / 219GB (44%)
Locale: de_DE.UTF-8
```

### Required:

- [X] I have searched past issues to see if this bug has already been reported.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `libqtile/widget/notify.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2011 Florian Mounier
3 # Copyright (c) 2011 Mounier Florian
4 # Copyright (c) 2012 roger
5 # Copyright (c) 2012-2014 Tycho Andersen
6 # Copyright (c) 2012-2013 Craig Barnes
7 # Copyright (c) 2013 Tao Sauvage
8 # Copyright (c) 2014 Sean Vig
9 # Copyright (c) 2014 Adi Sieker
10 # Copyright (c) 2020 elParaguayo
11 #
12 # Permission is hereby granted, free of charge, to any person obtaining a copy
13 # of this software and associated documentation files (the "Software"), to deal
14 # in the Software without restriction, including without limitation the rights
15 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 # copies of the Software, and to permit persons to whom the Software is
17 # furnished to do so, subject to the following conditions:
18 #
19 # The above copyright notice and this permission notice shall be included in
20 # all copies or substantial portions of the Software.
21 #
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 # SOFTWARE.
29 import asyncio
30 from os import path
31 
32 from libqtile import bar, pangocffi, utils
33 from libqtile.log_utils import logger
34 from libqtile.notify import ClosedReason, notifier
35 from libqtile.widget import base
36 
37 
38 class Notify(base._TextBox):
39     """
40     A notify widget
41 
42     This widget can handle actions provided by notification clients. However, only the
43     default action is supported, so if a client provides multiple actions then only the
44     default (first) action can be invoked. Some programs will provide their own
45     notification windows if the notification server does not support actions, so if you
46     want your notifications to handle more than one action then specify ``False`` for
47     the ``action`` option to disable all action handling. Unfortunately we cannot
48     specify the capability for exactly one action.
49     """
50 
51     defaults = [
52         ("foreground_urgent", "ff0000", "Foreground urgent priority colour"),
53         ("foreground_low", "dddddd", "Foreground low priority  colour"),
54         ("default_timeout", None, "Default timeout (seconds) for notifications"),
55         ("audiofile", None, "Audiofile played during notifications"),
56         ("action", True, "Enable handling of default action upon right click"),
57         (
58             "parse_text",
59             None,
60             "Function to parse and modify notifications. "
61             "e.g. function in config that removes line returns:"
62             "def my_func(text)"
63             "   return text.replace('\n', '')"
64             "then set option parse_text=my_func",
65         ),
66     ]
67     capabilities = {"body", "actions"}
68 
69     def __init__(self, width=bar.CALCULATED, **config):
70         base._TextBox.__init__(self, "", width, **config)
71         self.add_defaults(Notify.defaults)
72         self.current_id = 0
73 
74         default_callbacks = {
75             "Button1": self.clear,
76             "Button4": self.prev,
77             "Button5": self.next,
78         }
79         if self.action:
80             default_callbacks["Button3"] = self.invoke
81         else:
82             self.capabilities = Notify.capabilities.difference({"actions"})
83         self.add_callbacks(default_callbacks)
84 
85     def _configure(self, qtile, bar):
86         base._TextBox._configure(self, qtile, bar)
87         self.layout = self.drawer.textlayout(
88             self.text, self.foreground, self.font, self.fontsize, self.fontshadow, markup=True
89         )
90 
91     async def _config_async(self):
92         await notifier.register(self.update, self.capabilities, on_close=self.on_close)
93 
94     def set_notif_text(self, notif):
95         self.text = pangocffi.markup_escape_text(notif.summary)
96         urgency = getattr(notif.hints.get("urgency"), "value", 1)
97         if urgency != 1:
98             self.text = '<span color="%s">%s</span>' % (
99                 utils.hex(self.foreground_urgent if urgency == 2 else self.foreground_low),
100                 self.text,
101             )
102         if notif.body:
103             self.text = '<span weight="bold">%s</span> - %s' % (
104                 self.text,
105                 pangocffi.markup_escape_text(notif.body),
106             )
107         if callable(self.parse_text):
108             try:
109                 self.text = self.parse_text(self.text)
110             except:  # noqa: E722
111                 logger.exception("parse_text function failed:")
112         if self.audiofile and path.exists(self.audiofile):
113             self.qtile.cmd_spawn("aplay -q '%s'" % self.audiofile)
114 
115     def update(self, notif):
116         self.qtile.call_soon_threadsafe(self.real_update, notif)
117 
118     def real_update(self, notif):
119         self.set_notif_text(notif)
120         self.current_id = notif.id - 1
121         if notif.timeout and notif.timeout > 0:
122             self.timeout_add(
123                 notif.timeout / 1000, self.clear, method_args=(ClosedReason.expired,)
124             )
125         elif self.default_timeout:
126             self.timeout_add(
127                 self.default_timeout, self.clear, method_args=(ClosedReason.expired,)
128             )
129         self.bar.draw()
130         return True
131 
132     def display(self):
133         self.set_notif_text(notifier.notifications[self.current_id])
134         self.bar.draw()
135 
136     def clear(self, reason=ClosedReason.dismissed):
137         notifier._service.NotificationClosed(notifier.notifications[self.current_id].id, reason)
138         self.text = ""
139         self.current_id = len(notifier.notifications) - 1
140         self.bar.draw()
141 
142     def on_close(self, nid):
143         if self.current_id < len(notifier.notifications):
144             notif = notifier.notifications[self.current_id]
145             if notif.id == nid:
146                 self.clear(ClosedReason.method)
147 
148     def prev(self):
149         if self.current_id > 0:
150             self.current_id -= 1
151         self.display()
152 
153     def next(self):
154         if self.current_id < len(notifier.notifications) - 1:
155             self.current_id += 1
156             self.display()
157 
158     def invoke(self):
159         if self.current_id < len(notifier.notifications):
160             notif = notifier.notifications[self.current_id]
161             if notif.actions:
162                 notifier._service.ActionInvoked(notif.id, notif.actions[0])
163             self.clear()
164 
165     def cmd_display(self):
166         """Display the notifcication"""
167         self.display()
168 
169     def cmd_clear(self):
170         """Clear the notification"""
171         self.clear()
172 
173     def cmd_toggle(self):
174         """Toggle showing/clearing the notification"""
175         if self.text == "":
176             self.display()
177         else:
178             self.clear()
179 
180     def cmd_prev(self):
181         """Show previous notification"""
182         self.prev()
183 
184     def cmd_next(self):
185         """Show next notification"""
186         self.next()
187 
188     def cmd_invoke(self):
189         """Invoke the notification's default action"""
190         if self.action:
191             self.invoke()
192 
193     def finalize(self):
194         asyncio.create_task(self._finalize())
195 
196     async def _finalize(self):
197         task = notifier.unregister(self.update)
198 
199         # If the notifier has no more callbacks then it needs to be stopped.
200         # The returned task will handle the release of the service name from
201         # dbus. We await it here to make sure it's finished before we
202         # complete the finalisation of this widget.
203         if task:
204             await task
205         base._TextBox.finalize(self)
206
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/libqtile/widget/notify.py b/libqtile/widget/notify.py
--- a/libqtile/widget/notify.py
+++ b/libqtile/widget/notify.py
@@ -26,7 +26,6 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
-import asyncio
 from os import path
 
 from libqtile import bar, pangocffi, utils
@@ -89,6 +88,7 @@
         )
 
     async def _config_async(self):
+        logger.debug("Registering notify widget")
         await notifier.register(self.update, self.capabilities, on_close=self.on_close)
 
     def set_notif_text(self, notif):
@@ -191,7 +191,14 @@
             self.invoke()
 
     def finalize(self):
-        asyncio.create_task(self._finalize())
+        # We may need some async calls as part of the finalize call
+        # We run this with `call_soon_threadsafe` as this waits for
+        # the job to finish before continuing. This is important as,
+        # if the config is just reloading, we need to finish deregistering
+        # the notification server before the new Notify widget instance
+        # registers and creates a new server.
+        self.qtile.call_soon_threadsafe(self._finalize)
+        base._TextBox.finalize(self)
 
     async def _finalize(self):
         task = notifier.unregister(self.update)
@@ -202,4 +209,3 @@
         # complete the finalisation of this widget.
         if task:
             await task
-        base._TextBox.finalize(self)

{"golden_diff": "diff --git a/libqtile/widget/notify.py b/libqtile/widget/notify.py\n--- a/libqtile/widget/notify.py\n+++ b/libqtile/widget/notify.py\n@@ -26,7 +26,6 @@\n # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n # SOFTWARE.\n-import asyncio\n from os import path\n \n from libqtile import bar, pangocffi, utils\n@@ -89,6 +88,7 @@\n         )\n \n     async def _config_async(self):\n+        logger.debug(\"Registering notify widget\")\n         await notifier.register(self.update, self.capabilities, on_close=self.on_close)\n \n     def set_notif_text(self, notif):\n@@ -191,7 +191,14 @@\n             self.invoke()\n \n     def finalize(self):\n-        asyncio.create_task(self._finalize())\n+        # We may need some async calls as part of the finalize call\n+        # We run this with `call_soon_threadsafe` as this waits for\n+        # the job to finish before continuing. This is important as,\n+        # if the config is just reloading, we need to finish deregistering\n+        # the notification server before the new Notify widget instance\n+        # registers and creates a new server.\n+        self.qtile.call_soon_threadsafe(self._finalize)\n+        base._TextBox.finalize(self)\n \n     async def _finalize(self):\n         task = notifier.unregister(self.update)\n@@ -202,4 +209,3 @@\n         # complete the finalisation of this widget.\n         if task:\n             await task\n-        base._TextBox.finalize(self)\n", "issue": "notify-send stops working when Qtile config is reload instead restart, or if widget.Notify is disabled\n### The issue:\r\n\r\nnotify-send does not work anymore if Qtile config is reload, until I restart Qtile. It also will not work anymore and wait if I disable the \"widget.Notify\". I am not 100% sure if this is a problem caused by Qtile.\r\n\r\n1. Whenever I reload Qtile config instead of restarting, the next run of the program `notify-send` will hang and wait. I have to Ctrl+c to end the process. When I then restart Qtile, then the program `notify-send` works as expected.\r\n2. And i just tested to disable the widget.Notify and try again. Now The program always hangs, reload or not. `notify-send` seems not to find anything to send to. When I enable the widget, then it works like described at point 1.\r\n\r\nSome system information:\r\n\r\n- Your Qtile version (qtile --version): `0.21.1.dev8+gea4462b0`\r\n- Relevant logs from ~/.local/share/qtile/qtile.log: \r\n`2022-04-05 21:48:41,482 ERROR libqtile notify.py:_release():L183 Could not release org.freedesktop.Notifications.`\r\n\r\n```\r\nOS: Manjaro Linux [x86_64]\r\nKernel: 5.16.14-1-MANJARO\r\nUptime: 1 day, 17 hours, 36 mins\r\nPackages: 1896 (pacman)[stable], 1 (flatpak)\r\nShell: zsh 5.8.1\r\nResolution: 2560x1440 @ 144Hz\r\nDE: KDE Plasma 5.24.3\r\nWM: Qtile 0.21.1.dev8+gea4462b0\r\nTheme: breeze (GruvboxColors) [Plasma], Klaus [GTK2/3/4]\r\nIcons: breeze [Plasma], Gruvbox [GTK2/3/4]\r\nFont: Noto Sans (12pt) [Plasma], Noto Sans (12pt) [GTK2/3/4]\r\nCursor: default (24px)\r\nTerminal: kitty\r\nCPU: Intel Xeon E3-1230 v3 (8) @ 3.7GHz\r\nGPU: Nvidia GeForce GTX 1070\r\nMemory: 3225MiB / 15947MiB (20%)\r\nDisk (/): 98GB / 219GB (44%)\r\nLocale: de_DE.UTF-8\r\n```\r\n\r\n### Required:\r\n\r\n- [X] I have searched past issues to see if this bug has already been reported.\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) 2011 Florian Mounier\n# Copyright (c) 2011 Mounier Florian\n# Copyright (c) 2012 roger\n# Copyright (c) 2012-2014 Tycho Andersen\n# Copyright (c) 2012-2013 Craig Barnes\n# Copyright (c) 2013 Tao Sauvage\n# Copyright (c) 2014 Sean Vig\n# Copyright (c) 2014 Adi Sieker\n# Copyright (c) 2020 elParaguayo\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport asyncio\nfrom os import path\n\nfrom libqtile import bar, pangocffi, utils\nfrom libqtile.log_utils import logger\nfrom libqtile.notify import ClosedReason, notifier\nfrom libqtile.widget import base\n\n\nclass Notify(base._TextBox):\n    \"\"\"\n    A notify widget\n\n    This widget can handle actions provided by notification clients. However, only the\n    default action is supported, so if a client provides multiple actions then only the\n    default (first) action can be invoked. Some programs will provide their own\n    notification windows if the notification server does not support actions, so if you\n    want your notifications to handle more than one action then specify ``False`` for\n    the ``action`` option to disable all action handling. Unfortunately we cannot\n    specify the capability for exactly one action.\n    \"\"\"\n\n    defaults = [\n        (\"foreground_urgent\", \"ff0000\", \"Foreground urgent priority colour\"),\n        (\"foreground_low\", \"dddddd\", \"Foreground low priority  colour\"),\n        (\"default_timeout\", None, \"Default timeout (seconds) for notifications\"),\n        (\"audiofile\", None, \"Audiofile played during notifications\"),\n        (\"action\", True, \"Enable handling of default action upon right click\"),\n        (\n            \"parse_text\",\n            None,\n            \"Function to parse and modify notifications. \"\n            \"e.g. function in config that removes line returns:\"\n            \"def my_func(text)\"\n            \"   return text.replace('\\n', '')\"\n            \"then set option parse_text=my_func\",\n        ),\n    ]\n    capabilities = {\"body\", \"actions\"}\n\n    def __init__(self, width=bar.CALCULATED, **config):\n        base._TextBox.__init__(self, \"\", width, **config)\n        self.add_defaults(Notify.defaults)\n        self.current_id = 0\n\n        default_callbacks = {\n            \"Button1\": self.clear,\n            \"Button4\": self.prev,\n            \"Button5\": self.next,\n        }\n        if self.action:\n            default_callbacks[\"Button3\"] = self.invoke\n        else:\n            self.capabilities = Notify.capabilities.difference({\"actions\"})\n        self.add_callbacks(default_callbacks)\n\n    def _configure(self, qtile, bar):\n        base._TextBox._configure(self, qtile, bar)\n        self.layout = self.drawer.textlayout(\n            self.text, self.foreground, self.font, self.fontsize, self.fontshadow, markup=True\n        )\n\n    async def _config_async(self):\n        await notifier.register(self.update, self.capabilities, on_close=self.on_close)\n\n    def set_notif_text(self, notif):\n        self.text = pangocffi.markup_escape_text(notif.summary)\n        urgency = getattr(notif.hints.get(\"urgency\"), \"value\", 1)\n        if urgency != 1:\n            self.text = '<span color=\"%s\">%s</span>' % (\n                utils.hex(self.foreground_urgent if urgency == 2 else self.foreground_low),\n                self.text,\n            )\n        if notif.body:\n            self.text = '<span weight=\"bold\">%s</span> - %s' % (\n                self.text,\n                pangocffi.markup_escape_text(notif.body),\n            )\n        if callable(self.parse_text):\n            try:\n                self.text = self.parse_text(self.text)\n            except:  # noqa: E722\n                logger.exception(\"parse_text function failed:\")\n        if self.audiofile and path.exists(self.audiofile):\n            self.qtile.cmd_spawn(\"aplay -q '%s'\" % self.audiofile)\n\n    def update(self, notif):\n        self.qtile.call_soon_threadsafe(self.real_update, notif)\n\n    def real_update(self, notif):\n        self.set_notif_text(notif)\n        self.current_id = notif.id - 1\n        if notif.timeout and notif.timeout > 0:\n            self.timeout_add(\n                notif.timeout / 1000, self.clear, method_args=(ClosedReason.expired,)\n            )\n        elif self.default_timeout:\n            self.timeout_add(\n                self.default_timeout, self.clear, method_args=(ClosedReason.expired,)\n            )\n        self.bar.draw()\n        return True\n\n    def display(self):\n        self.set_notif_text(notifier.notifications[self.current_id])\n        self.bar.draw()\n\n    def clear(self, reason=ClosedReason.dismissed):\n        notifier._service.NotificationClosed(notifier.notifications[self.current_id].id, reason)\n        self.text = \"\"\n        self.current_id = len(notifier.notifications) - 1\n        self.bar.draw()\n\n    def on_close(self, nid):\n        if self.current_id < len(notifier.notifications):\n            notif = notifier.notifications[self.current_id]\n            if notif.id == nid:\n                self.clear(ClosedReason.method)\n\n    def prev(self):\n        if self.current_id > 0:\n            self.current_id -= 1\n        self.display()\n\n    def next(self):\n        if self.current_id < len(notifier.notifications) - 1:\n            self.current_id += 1\n            self.display()\n\n    def invoke(self):\n        if self.current_id < len(notifier.notifications):\n            notif = notifier.notifications[self.current_id]\n            if notif.actions:\n                notifier._service.ActionInvoked(notif.id, notif.actions[0])\n            self.clear()\n\n    def cmd_display(self):\n        \"\"\"Display the notifcication\"\"\"\n        self.display()\n\n    def cmd_clear(self):\n        \"\"\"Clear the notification\"\"\"\n        self.clear()\n\n    def cmd_toggle(self):\n        \"\"\"Toggle showing/clearing the notification\"\"\"\n        if self.text == \"\":\n            self.display()\n        else:\n            self.clear()\n\n    def cmd_prev(self):\n        \"\"\"Show previous notification\"\"\"\n        self.prev()\n\n    def cmd_next(self):\n        \"\"\"Show next notification\"\"\"\n        self.next()\n\n    def cmd_invoke(self):\n        \"\"\"Invoke the notification's default action\"\"\"\n        if self.action:\n            self.invoke()\n\n    def finalize(self):\n        asyncio.create_task(self._finalize())\n\n    async def _finalize(self):\n        task = notifier.unregister(self.update)\n\n        # If the notifier has no more callbacks then it needs to be stopped.\n        # The returned task will handle the release of the service name from\n        # dbus. We await it here to make sure it's finished before we\n        # complete the finalisation of this widget.\n        if task:\n            await task\n        base._TextBox.finalize(self)\n", "path": "libqtile/widget/notify.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) 2011 Florian Mounier\n# Copyright (c) 2011 Mounier Florian\n# Copyright (c) 2012 roger\n# Copyright (c) 2012-2014 Tycho Andersen\n# Copyright (c) 2012-2013 Craig Barnes\n# Copyright (c) 2013 Tao Sauvage\n# Copyright (c) 2014 Sean Vig\n# Copyright (c) 2014 Adi Sieker\n# Copyright (c) 2020 elParaguayo\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom os import path\n\nfrom libqtile import bar, pangocffi, utils\nfrom libqtile.log_utils import logger\nfrom libqtile.notify import ClosedReason, notifier\nfrom libqtile.widget import base\n\n\nclass Notify(base._TextBox):\n    \"\"\"\n    A notify widget\n\n    This widget can handle actions provided by notification clients. However, only the\n    default action is supported, so if a client provides multiple actions then only the\n    default (first) action can be invoked. Some programs will provide their own\n    notification windows if the notification server does not support actions, so if you\n    want your notifications to handle more than one action then specify ``False`` for\n    the ``action`` option to disable all action handling. Unfortunately we cannot\n    specify the capability for exactly one action.\n    \"\"\"\n\n    defaults = [\n        (\"foreground_urgent\", \"ff0000\", \"Foreground urgent priority colour\"),\n        (\"foreground_low\", \"dddddd\", \"Foreground low priority  colour\"),\n        (\"default_timeout\", None, \"Default timeout (seconds) for notifications\"),\n        (\"audiofile\", None, \"Audiofile played during notifications\"),\n        (\"action\", True, \"Enable handling of default action upon right click\"),\n        (\n            \"parse_text\",\n            None,\n            \"Function to parse and modify notifications. \"\n            \"e.g. function in config that removes line returns:\"\n            \"def my_func(text)\"\n            \"   return text.replace('\\n', '')\"\n            \"then set option parse_text=my_func\",\n        ),\n    ]\n    capabilities = {\"body\", \"actions\"}\n\n    def __init__(self, width=bar.CALCULATED, **config):\n        base._TextBox.__init__(self, \"\", width, **config)\n        self.add_defaults(Notify.defaults)\n        self.current_id = 0\n\n        default_callbacks = {\n            \"Button1\": self.clear,\n            \"Button4\": self.prev,\n            \"Button5\": self.next,\n        }\n        if self.action:\n            default_callbacks[\"Button3\"] = self.invoke\n        else:\n            self.capabilities = Notify.capabilities.difference({\"actions\"})\n        self.add_callbacks(default_callbacks)\n\n    def _configure(self, qtile, bar):\n        base._TextBox._configure(self, qtile, bar)\n        self.layout = self.drawer.textlayout(\n            self.text, self.foreground, self.font, self.fontsize, self.fontshadow, markup=True\n        )\n\n    async def _config_async(self):\n        logger.debug(\"Registering notify widget\")\n        await notifier.register(self.update, self.capabilities, on_close=self.on_close)\n\n    def set_notif_text(self, notif):\n        self.text = pangocffi.markup_escape_text(notif.summary)\n        urgency = getattr(notif.hints.get(\"urgency\"), \"value\", 1)\n        if urgency != 1:\n            self.text = '<span color=\"%s\">%s</span>' % (\n                utils.hex(self.foreground_urgent if urgency == 2 else self.foreground_low),\n                self.text,\n            )\n        if notif.body:\n            self.text = '<span weight=\"bold\">%s</span> - %s' % (\n                self.text,\n                pangocffi.markup_escape_text(notif.body),\n            )\n        if callable(self.parse_text):\n            try:\n                self.text = self.parse_text(self.text)\n            except:  # noqa: E722\n                logger.exception(\"parse_text function failed:\")\n        if self.audiofile and path.exists(self.audiofile):\n            self.qtile.cmd_spawn(\"aplay -q '%s'\" % self.audiofile)\n\n    def update(self, notif):\n        self.qtile.call_soon_threadsafe(self.real_update, notif)\n\n    def real_update(self, notif):\n        self.set_notif_text(notif)\n        self.current_id = notif.id - 1\n        if notif.timeout and notif.timeout > 0:\n            self.timeout_add(\n                notif.timeout / 1000, self.clear, method_args=(ClosedReason.expired,)\n            )\n        elif self.default_timeout:\n            self.timeout_add(\n                self.default_timeout, self.clear, method_args=(ClosedReason.expired,)\n            )\n        self.bar.draw()\n        return True\n\n    def display(self):\n        self.set_notif_text(notifier.notifications[self.current_id])\n        self.bar.draw()\n\n    def clear(self, reason=ClosedReason.dismissed):\n        notifier._service.NotificationClosed(notifier.notifications[self.current_id].id, reason)\n        self.text = \"\"\n        self.current_id = len(notifier.notifications) - 1\n        self.bar.draw()\n\n    def on_close(self, nid):\n        if self.current_id < len(notifier.notifications):\n            notif = notifier.notifications[self.current_id]\n            if notif.id == nid:\n                self.clear(ClosedReason.method)\n\n    def prev(self):\n        if self.current_id > 0:\n            self.current_id -= 1\n        self.display()\n\n    def next(self):\n        if self.current_id < len(notifier.notifications) - 1:\n            self.current_id += 1\n            self.display()\n\n    def invoke(self):\n        if self.current_id < len(notifier.notifications):\n            notif = notifier.notifications[self.current_id]\n            if notif.actions:\n                notifier._service.ActionInvoked(notif.id, notif.actions[0])\n            self.clear()\n\n    def cmd_display(self):\n        \"\"\"Display the notifcication\"\"\"\n        self.display()\n\n    def cmd_clear(self):\n        \"\"\"Clear the notification\"\"\"\n        self.clear()\n\n    def cmd_toggle(self):\n        \"\"\"Toggle showing/clearing the notification\"\"\"\n        if self.text == \"\":\n            self.display()\n        else:\n            self.clear()\n\n    def cmd_prev(self):\n        \"\"\"Show previous notification\"\"\"\n        self.prev()\n\n    def cmd_next(self):\n        \"\"\"Show next notification\"\"\"\n        self.next()\n\n    def cmd_invoke(self):\n        \"\"\"Invoke the notification's default action\"\"\"\n        if self.action:\n            self.invoke()\n\n    def finalize(self):\n        # We may need some async calls as part of the finalize call\n        # We run this with `call_soon_threadsafe` as this waits for\n        # the job to finish before continuing. This is important as,\n        # if the config is just reloading, we need to finish deregistering\n        # the notification server before the new Notify widget instance\n        # registers and creates a new server.\n        self.qtile.call_soon_threadsafe(self._finalize)\n        base._TextBox.finalize(self)\n\n    async def _finalize(self):\n        task = notifier.unregister(self.update)\n\n        # If the notifier has no more callbacks then it needs to be stopped.\n        # The returned task will handle the release of the service name from\n        # dbus. We await it here to make sure it's finished before we\n        # complete the finalisation of this widget.\n        if task:\n            await task\n", "path": "libqtile/widget/notify.py"}]}