We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
multiple systray widgets are racy
The Distrotube Guy reports via https://www.youtube.com/watch?v=9NO2DJARbU0 that multiple systrays are racy.

The spec says: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.1.html#locating

that we should use `_NET_SYSETM_TRAY_Sn` for the nth system tray widget, but I think our code calculating this is wrong:

https://github.com/qtile/qtile/blob/master/libqtile/widget/systray.py#L129

This code always uses qtile's current screen, instead of the screen that the bar is on. It should at least use the right value for `n`.

Perhaps instead, we could make multiple Systray() objects be an error, and just have people specify one and let it mirror everywhere.

--- 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/systray.py`
Content:
```
1 # Copyright (c) 2010 Aldo Cortesi
2 # Copyright (c) 2010-2011 dequis
3 # Copyright (c) 2010, 2012 roger
4 # Copyright (c) 2011 Mounier Florian
5 # Copyright (c) 2011-2012, 2014 Tycho Andersen
6 # Copyright (c) 2012 dmpayton
7 # Copyright (c) 2012-2013 Craig Barnes
8 # Copyright (c) 2013 hbc
9 # Copyright (c) 2013 Tao Sauvage
10 # Copyright (c) 2014 Sean Vig
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 from typing import TYPE_CHECKING
30 
31 import xcffib
32 from xcffib.xproto import ClientMessageData, ClientMessageEvent, EventMask, SetMode
33 
34 from libqtile import bar
35 from libqtile.backend.x11 import window
36 from libqtile.widget import base
37 
38 if TYPE_CHECKING:
39     from typing import List, Optional
40 
41 XEMBED_PROTOCOL_VERSION = 0
42 
43 
44 class Icon(window._Window):
45     _window_mask = EventMask.StructureNotify | EventMask.PropertyChange | EventMask.Exposure
46 
47     def __init__(self, win, qtile, systray):
48         window._Window.__init__(self, win, qtile)
49         self.systray = systray
50         # win.get_name() may return None when apps provide a temporary window before the icon window
51         # we need something in self.name in order to sort icons so we use the window's WID.
52         self.name = win.get_name() or str(win.wid)
53         self.update_size()
54 
55     def __eq__(self, other):
56         if not isinstance(other, Icon):
57             return False
58 
59         return self.window.wid == other.window.wid
60 
61     def update_size(self):
62         icon_size = self.systray.icon_size
63         self.update_hints()
64 
65         width = self.hints.get("min_width", icon_size)
66         height = self.hints.get("min_height", icon_size)
67 
68         width = max(width, icon_size)
69         height = max(height, icon_size)
70 
71         if height > icon_size:
72             width = width * icon_size // height
73             height = icon_size
74 
75         self.width = width
76         self.height = height
77         return False
78 
79     def handle_PropertyNotify(self, e):  # noqa: N802
80         name = self.qtile.core.conn.atoms.get_name(e.atom)
81         if name == "_XEMBED_INFO":
82             info = self.window.get_property("_XEMBED_INFO", unpack=int)
83             if info and info[1]:
84                 self.systray.bar.draw()
85 
86         return False
87 
88     def handle_DestroyNotify(self, event):  # noqa: N802
89         wid = event.window
90         icon = self.qtile.windows_map.pop(wid)
91         self.systray.tray_icons.remove(icon)
92         self.systray.bar.draw()
93         return False
94 
95     handle_UnmapNotify = handle_DestroyNotify  # noqa: N815
96 
97 
98 class Systray(window._Window, base._Widget):
99     """
100     A widget that manages system tray.
101 
102     .. note::
103         Icons will not render correctly where the bar/widget is
104         drawn with a semi-transparent background. Instead, icons
105         will be drawn with a transparent background.
106 
107         If using this widget it is therefore recommended to use
108         a fully opaque background colour or a fully transparent
109         one.
110     """
111 
112     _window_mask = EventMask.StructureNotify | EventMask.Exposure
113 
114     orientations = base.ORIENTATION_BOTH
115 
116     defaults = [
117         ("icon_size", 20, "Icon width"),
118         ("padding", 5, "Padding between icons"),
119     ]
120 
121     def __init__(self, **config):
122         base._Widget.__init__(self, bar.CALCULATED, **config)
123         self.add_defaults(Systray.defaults)
124         self.tray_icons = []
125         self.screen = 0
126         self._name = config.get("name", "systray")
127         self._wm_class: Optional[List[str]] = None
128 
129     def calculate_length(self):
130         if self.bar.horizontal:
131             length = sum(i.width for i in self.tray_icons)
132         else:
133             length = sum(i.height for i in self.tray_icons)
134         length += self.padding * len(self.tray_icons)
135         return length
136 
137     def _configure(self, qtile, bar):
138         base._Widget._configure(self, qtile, bar)
139 
140         if self.configured:
141             return
142 
143         self.conn = conn = qtile.core.conn
144         win = conn.create_window(-1, -1, 1, 1)
145         window._Window.__init__(self, window.XWindow(conn, win.wid), qtile)
146         qtile.windows_map[win.wid] = self
147 
148         # window._Window.__init__ overwrites the widget name so we need to restore it
149         self.name = self._name
150 
151         # Even when we have multiple "Screen"s, we are setting up as the system
152         # tray on a particular X display, that is the screen we need to
153         # reference in the atom
154         if qtile.current_screen:
155             self.screen = qtile.current_screen.index
156         self.bar = bar
157         atoms = conn.atoms
158 
159         # We need tray to tell icons which visual to use.
160         # This needs to be the same as the bar/widget.
161         # This mainly benefits transparent bars.
162         conn.conn.core.ChangeProperty(
163             xcffib.xproto.PropMode.Replace,
164             win.wid,
165             atoms["_NET_SYSTEM_TRAY_VISUAL"],
166             xcffib.xproto.Atom.VISUALID,
167             32,
168             1,
169             [self.drawer._visual.visual_id],
170         )
171 
172         conn.conn.core.SetSelectionOwner(
173             win.wid, atoms["_NET_SYSTEM_TRAY_S{:d}".format(self.screen)], xcffib.CurrentTime
174         )
175         data = [
176             xcffib.CurrentTime,
177             atoms["_NET_SYSTEM_TRAY_S{:d}".format(self.screen)],
178             win.wid,
179             0,
180             0,
181         ]
182         union = ClientMessageData.synthetic(data, "I" * 5)
183         event = ClientMessageEvent.synthetic(
184             format=32, window=qtile.core._root.wid, type=atoms["MANAGER"], data=union
185         )
186         qtile.core._root.send_event(event, mask=EventMask.StructureNotify)
187 
188     def handle_ClientMessage(self, event):  # noqa: N802
189         atoms = self.conn.atoms
190 
191         opcode = event.type
192         data = event.data.data32
193         message = data[1]
194         wid = data[2]
195 
196         parent = self.bar.window.window
197 
198         if opcode == atoms["_NET_SYSTEM_TRAY_OPCODE"] and message == 0:
199             w = window.XWindow(self.conn, wid)
200             icon = Icon(w, self.qtile, self)
201             if icon not in self.tray_icons:
202                 self.tray_icons.append(icon)
203                 self.tray_icons.sort(key=lambda icon: icon.name)
204                 self.qtile.windows_map[wid] = icon
205 
206             self.conn.conn.core.ChangeSaveSet(SetMode.Insert, wid)
207             self.conn.conn.core.ReparentWindow(wid, parent.wid, 0, 0)
208             self.conn.conn.flush()
209 
210             info = icon.window.get_property("_XEMBED_INFO", unpack=int)
211 
212             if not info:
213                 self.bar.draw()
214                 return False
215 
216             if info[1]:
217                 self.bar.draw()
218 
219         return False
220 
221     def draw(self):
222         offset = self.padding
223         self.drawer.clear(self.background or self.bar.background)
224         self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)
225         for pos, icon in enumerate(self.tray_icons):
226             icon.window.set_attribute(backpixmap=self.drawer.pixmap)
227             if self.bar.horizontal:
228                 xoffset = self.offsetx + offset
229                 yoffset = self.bar.height // 2 - self.icon_size // 2 + self.offsety
230                 step = icon.width
231             else:
232                 xoffset = self.bar.width // 2 - self.icon_size // 2 + self.offsetx
233                 yoffset = self.offsety + offset
234                 step = icon.height
235 
236             icon.place(xoffset, yoffset, icon.width, self.icon_size, 0, None)
237             if icon.hidden:
238                 icon.unhide()
239                 data = [
240                     self.conn.atoms["_XEMBED_EMBEDDED_NOTIFY"],
241                     xcffib.xproto.Time.CurrentTime,
242                     0,
243                     self.bar.window.wid,
244                     XEMBED_PROTOCOL_VERSION,
245                 ]
246                 u = xcffib.xproto.ClientMessageData.synthetic(data, "I" * 5)
247                 event = xcffib.xproto.ClientMessageEvent.synthetic(
248                     format=32, window=icon.wid, type=self.conn.atoms["_XEMBED"], data=u
249                 )
250                 self.window.send_event(event)
251 
252             offset += step + self.padding
253 
254     def finalize(self):
255         base._Widget.finalize(self)
256         atoms = self.conn.atoms
257         self.conn.conn.core.SetSelectionOwner(
258             0,
259             atoms["_NET_SYSTEM_TRAY_S{:d}".format(self.screen)],
260             xcffib.CurrentTime,
261         )
262         self.hide()
263 
264         root = self.qtile.core._root.wid
265         for icon in self.tray_icons:
266             self.conn.conn.core.ReparentWindow(icon.window.wid, root, 0, 0)
267         self.conn.conn.flush()
268 
269         del self.qtile.windows_map[self.wid]
270         self.conn.conn.core.DestroyWindow(self.wid)
271
```
--- 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/systray.py b/libqtile/widget/systray.py
--- a/libqtile/widget/systray.py
+++ b/libqtile/widget/systray.py
@@ -33,6 +33,7 @@
 
 from libqtile import bar
 from libqtile.backend.x11 import window
+from libqtile.confreader import ConfigError
 from libqtile.widget import base
 
 if TYPE_CHECKING:
@@ -99,6 +100,9 @@
     """
     A widget that manages system tray.
 
+    Only one Systray widget is allowed. Adding additional Systray
+    widgets will result in a ConfigError.
+
     .. note::
         Icons will not render correctly where the bar/widget is
         drawn with a semi-transparent background. Instead, icons
@@ -109,6 +113,8 @@
         one.
     """
 
+    _instances = 0
+
     _window_mask = EventMask.StructureNotify | EventMask.Exposure
 
     orientations = base.ORIENTATION_BOTH
@@ -137,6 +143,9 @@
     def _configure(self, qtile, bar):
         base._Widget._configure(self, qtile, bar)
 
+        if Systray._instances > 0:
+            raise ConfigError("Only one Systray can be used.")
+
         if self.configured:
             return
 
@@ -185,6 +194,17 @@
         )
         qtile.core._root.send_event(event, mask=EventMask.StructureNotify)
 
+        Systray._instances += 1
+
+    def create_mirror(self):
+        """
+        Systray cannot be mirrored as we do not use a Drawer object to render icons.
+
+        Return itself so that, when the bar tries to configure it again, a ConfigError
+        is raised.
+        """
+        return self
+
     def handle_ClientMessage(self, event):  # noqa: N802
         atoms = self.conn.atoms
 
@@ -268,3 +288,5 @@
 
         del self.qtile.windows_map[self.wid]
         self.conn.conn.core.DestroyWindow(self.wid)
+
+        Systray._instances -= 1

{"golden_diff": "diff --git a/libqtile/widget/systray.py b/libqtile/widget/systray.py\n--- a/libqtile/widget/systray.py\n+++ b/libqtile/widget/systray.py\n@@ -33,6 +33,7 @@\n \n from libqtile import bar\n from libqtile.backend.x11 import window\n+from libqtile.confreader import ConfigError\n from libqtile.widget import base\n \n if TYPE_CHECKING:\n@@ -99,6 +100,9 @@\n     \"\"\"\n     A widget that manages system tray.\n \n+    Only one Systray widget is allowed. Adding additional Systray\n+    widgets will result in a ConfigError.\n+\n     .. note::\n         Icons will not render correctly where the bar/widget is\n         drawn with a semi-transparent background. Instead, icons\n@@ -109,6 +113,8 @@\n         one.\n     \"\"\"\n \n+    _instances = 0\n+\n     _window_mask = EventMask.StructureNotify | EventMask.Exposure\n \n     orientations = base.ORIENTATION_BOTH\n@@ -137,6 +143,9 @@\n     def _configure(self, qtile, bar):\n         base._Widget._configure(self, qtile, bar)\n \n+        if Systray._instances > 0:\n+            raise ConfigError(\"Only one Systray can be used.\")\n+\n         if self.configured:\n             return\n \n@@ -185,6 +194,17 @@\n         )\n         qtile.core._root.send_event(event, mask=EventMask.StructureNotify)\n \n+        Systray._instances += 1\n+\n+    def create_mirror(self):\n+        \"\"\"\n+        Systray cannot be mirrored as we do not use a Drawer object to render icons.\n+\n+        Return itself so that, when the bar tries to configure it again, a ConfigError\n+        is raised.\n+        \"\"\"\n+        return self\n+\n     def handle_ClientMessage(self, event):  # noqa: N802\n         atoms = self.conn.atoms\n \n@@ -268,3 +288,5 @@\n \n         del self.qtile.windows_map[self.wid]\n         self.conn.conn.core.DestroyWindow(self.wid)\n+\n+        Systray._instances -= 1\n", "issue": "multiple systray widgets are racy\nThe Distrotube Guy reports via https://www.youtube.com/watch?v=9NO2DJARbU0 that multiple systrays are racy.\r\n\r\nThe spec says: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.1.html#locating\r\n\r\nthat we should use `_NET_SYSETM_TRAY_Sn` for the nth system tray widget, but I think our code calculating this is wrong:\r\n\r\nhttps://github.com/qtile/qtile/blob/master/libqtile/widget/systray.py#L129\r\n\r\nThis code always uses qtile's current screen, instead of the screen that the bar is on. It should at least use the right value for `n`.\r\n\r\nPerhaps instead, we could make multiple Systray() objects be an error, and just have people specify one and let it mirror everywhere.\n", "before_files": [{"content": "# Copyright (c) 2010 Aldo Cortesi\n# Copyright (c) 2010-2011 dequis\n# Copyright (c) 2010, 2012 roger\n# Copyright (c) 2011 Mounier Florian\n# Copyright (c) 2011-2012, 2014 Tycho Andersen\n# Copyright (c) 2012 dmpayton\n# Copyright (c) 2012-2013 Craig Barnes\n# Copyright (c) 2013 hbc\n# Copyright (c) 2013 Tao Sauvage\n# Copyright (c) 2014 Sean Vig\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 typing import TYPE_CHECKING\n\nimport xcffib\nfrom xcffib.xproto import ClientMessageData, ClientMessageEvent, EventMask, SetMode\n\nfrom libqtile import bar\nfrom libqtile.backend.x11 import window\nfrom libqtile.widget import base\n\nif TYPE_CHECKING:\n    from typing import List, Optional\n\nXEMBED_PROTOCOL_VERSION = 0\n\n\nclass Icon(window._Window):\n    _window_mask = EventMask.StructureNotify | EventMask.PropertyChange | EventMask.Exposure\n\n    def __init__(self, win, qtile, systray):\n        window._Window.__init__(self, win, qtile)\n        self.systray = systray\n        # win.get_name() may return None when apps provide a temporary window before the icon window\n        # we need something in self.name in order to sort icons so we use the window's WID.\n        self.name = win.get_name() or str(win.wid)\n        self.update_size()\n\n    def __eq__(self, other):\n        if not isinstance(other, Icon):\n            return False\n\n        return self.window.wid == other.window.wid\n\n    def update_size(self):\n        icon_size = self.systray.icon_size\n        self.update_hints()\n\n        width = self.hints.get(\"min_width\", icon_size)\n        height = self.hints.get(\"min_height\", icon_size)\n\n        width = max(width, icon_size)\n        height = max(height, icon_size)\n\n        if height > icon_size:\n            width = width * icon_size // height\n            height = icon_size\n\n        self.width = width\n        self.height = height\n        return False\n\n    def handle_PropertyNotify(self, e):  # noqa: N802\n        name = self.qtile.core.conn.atoms.get_name(e.atom)\n        if name == \"_XEMBED_INFO\":\n            info = self.window.get_property(\"_XEMBED_INFO\", unpack=int)\n            if info and info[1]:\n                self.systray.bar.draw()\n\n        return False\n\n    def handle_DestroyNotify(self, event):  # noqa: N802\n        wid = event.window\n        icon = self.qtile.windows_map.pop(wid)\n        self.systray.tray_icons.remove(icon)\n        self.systray.bar.draw()\n        return False\n\n    handle_UnmapNotify = handle_DestroyNotify  # noqa: N815\n\n\nclass Systray(window._Window, base._Widget):\n    \"\"\"\n    A widget that manages system tray.\n\n    .. note::\n        Icons will not render correctly where the bar/widget is\n        drawn with a semi-transparent background. Instead, icons\n        will be drawn with a transparent background.\n\n        If using this widget it is therefore recommended to use\n        a fully opaque background colour or a fully transparent\n        one.\n    \"\"\"\n\n    _window_mask = EventMask.StructureNotify | EventMask.Exposure\n\n    orientations = base.ORIENTATION_BOTH\n\n    defaults = [\n        (\"icon_size\", 20, \"Icon width\"),\n        (\"padding\", 5, \"Padding between icons\"),\n    ]\n\n    def __init__(self, **config):\n        base._Widget.__init__(self, bar.CALCULATED, **config)\n        self.add_defaults(Systray.defaults)\n        self.tray_icons = []\n        self.screen = 0\n        self._name = config.get(\"name\", \"systray\")\n        self._wm_class: Optional[List[str]] = None\n\n    def calculate_length(self):\n        if self.bar.horizontal:\n            length = sum(i.width for i in self.tray_icons)\n        else:\n            length = sum(i.height for i in self.tray_icons)\n        length += self.padding * len(self.tray_icons)\n        return length\n\n    def _configure(self, qtile, bar):\n        base._Widget._configure(self, qtile, bar)\n\n        if self.configured:\n            return\n\n        self.conn = conn = qtile.core.conn\n        win = conn.create_window(-1, -1, 1, 1)\n        window._Window.__init__(self, window.XWindow(conn, win.wid), qtile)\n        qtile.windows_map[win.wid] = self\n\n        # window._Window.__init__ overwrites the widget name so we need to restore it\n        self.name = self._name\n\n        # Even when we have multiple \"Screen\"s, we are setting up as the system\n        # tray on a particular X display, that is the screen we need to\n        # reference in the atom\n        if qtile.current_screen:\n            self.screen = qtile.current_screen.index\n        self.bar = bar\n        atoms = conn.atoms\n\n        # We need tray to tell icons which visual to use.\n        # This needs to be the same as the bar/widget.\n        # This mainly benefits transparent bars.\n        conn.conn.core.ChangeProperty(\n            xcffib.xproto.PropMode.Replace,\n            win.wid,\n            atoms[\"_NET_SYSTEM_TRAY_VISUAL\"],\n            xcffib.xproto.Atom.VISUALID,\n            32,\n            1,\n            [self.drawer._visual.visual_id],\n        )\n\n        conn.conn.core.SetSelectionOwner(\n            win.wid, atoms[\"_NET_SYSTEM_TRAY_S{:d}\".format(self.screen)], xcffib.CurrentTime\n        )\n        data = [\n            xcffib.CurrentTime,\n            atoms[\"_NET_SYSTEM_TRAY_S{:d}\".format(self.screen)],\n            win.wid,\n            0,\n            0,\n        ]\n        union = ClientMessageData.synthetic(data, \"I\" * 5)\n        event = ClientMessageEvent.synthetic(\n            format=32, window=qtile.core._root.wid, type=atoms[\"MANAGER\"], data=union\n        )\n        qtile.core._root.send_event(event, mask=EventMask.StructureNotify)\n\n    def handle_ClientMessage(self, event):  # noqa: N802\n        atoms = self.conn.atoms\n\n        opcode = event.type\n        data = event.data.data32\n        message = data[1]\n        wid = data[2]\n\n        parent = self.bar.window.window\n\n        if opcode == atoms[\"_NET_SYSTEM_TRAY_OPCODE\"] and message == 0:\n            w = window.XWindow(self.conn, wid)\n            icon = Icon(w, self.qtile, self)\n            if icon not in self.tray_icons:\n                self.tray_icons.append(icon)\n                self.tray_icons.sort(key=lambda icon: icon.name)\n                self.qtile.windows_map[wid] = icon\n\n            self.conn.conn.core.ChangeSaveSet(SetMode.Insert, wid)\n            self.conn.conn.core.ReparentWindow(wid, parent.wid, 0, 0)\n            self.conn.conn.flush()\n\n            info = icon.window.get_property(\"_XEMBED_INFO\", unpack=int)\n\n            if not info:\n                self.bar.draw()\n                return False\n\n            if info[1]:\n                self.bar.draw()\n\n        return False\n\n    def draw(self):\n        offset = self.padding\n        self.drawer.clear(self.background or self.bar.background)\n        self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)\n        for pos, icon in enumerate(self.tray_icons):\n            icon.window.set_attribute(backpixmap=self.drawer.pixmap)\n            if self.bar.horizontal:\n                xoffset = self.offsetx + offset\n                yoffset = self.bar.height // 2 - self.icon_size // 2 + self.offsety\n                step = icon.width\n            else:\n                xoffset = self.bar.width // 2 - self.icon_size // 2 + self.offsetx\n                yoffset = self.offsety + offset\n                step = icon.height\n\n            icon.place(xoffset, yoffset, icon.width, self.icon_size, 0, None)\n            if icon.hidden:\n                icon.unhide()\n                data = [\n                    self.conn.atoms[\"_XEMBED_EMBEDDED_NOTIFY\"],\n                    xcffib.xproto.Time.CurrentTime,\n                    0,\n                    self.bar.window.wid,\n                    XEMBED_PROTOCOL_VERSION,\n                ]\n                u = xcffib.xproto.ClientMessageData.synthetic(data, \"I\" * 5)\n                event = xcffib.xproto.ClientMessageEvent.synthetic(\n                    format=32, window=icon.wid, type=self.conn.atoms[\"_XEMBED\"], data=u\n                )\n                self.window.send_event(event)\n\n            offset += step + self.padding\n\n    def finalize(self):\n        base._Widget.finalize(self)\n        atoms = self.conn.atoms\n        self.conn.conn.core.SetSelectionOwner(\n            0,\n            atoms[\"_NET_SYSTEM_TRAY_S{:d}\".format(self.screen)],\n            xcffib.CurrentTime,\n        )\n        self.hide()\n\n        root = self.qtile.core._root.wid\n        for icon in self.tray_icons:\n            self.conn.conn.core.ReparentWindow(icon.window.wid, root, 0, 0)\n        self.conn.conn.flush()\n\n        del self.qtile.windows_map[self.wid]\n        self.conn.conn.core.DestroyWindow(self.wid)\n", "path": "libqtile/widget/systray.py"}], "after_files": [{"content": "# Copyright (c) 2010 Aldo Cortesi\n# Copyright (c) 2010-2011 dequis\n# Copyright (c) 2010, 2012 roger\n# Copyright (c) 2011 Mounier Florian\n# Copyright (c) 2011-2012, 2014 Tycho Andersen\n# Copyright (c) 2012 dmpayton\n# Copyright (c) 2012-2013 Craig Barnes\n# Copyright (c) 2013 hbc\n# Copyright (c) 2013 Tao Sauvage\n# Copyright (c) 2014 Sean Vig\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 typing import TYPE_CHECKING\n\nimport xcffib\nfrom xcffib.xproto import ClientMessageData, ClientMessageEvent, EventMask, SetMode\n\nfrom libqtile import bar\nfrom libqtile.backend.x11 import window\nfrom libqtile.confreader import ConfigError\nfrom libqtile.widget import base\n\nif TYPE_CHECKING:\n    from typing import List, Optional\n\nXEMBED_PROTOCOL_VERSION = 0\n\n\nclass Icon(window._Window):\n    _window_mask = EventMask.StructureNotify | EventMask.PropertyChange | EventMask.Exposure\n\n    def __init__(self, win, qtile, systray):\n        window._Window.__init__(self, win, qtile)\n        self.systray = systray\n        # win.get_name() may return None when apps provide a temporary window before the icon window\n        # we need something in self.name in order to sort icons so we use the window's WID.\n        self.name = win.get_name() or str(win.wid)\n        self.update_size()\n\n    def __eq__(self, other):\n        if not isinstance(other, Icon):\n            return False\n\n        return self.window.wid == other.window.wid\n\n    def update_size(self):\n        icon_size = self.systray.icon_size\n        self.update_hints()\n\n        width = self.hints.get(\"min_width\", icon_size)\n        height = self.hints.get(\"min_height\", icon_size)\n\n        width = max(width, icon_size)\n        height = max(height, icon_size)\n\n        if height > icon_size:\n            width = width * icon_size // height\n            height = icon_size\n\n        self.width = width\n        self.height = height\n        return False\n\n    def handle_PropertyNotify(self, e):  # noqa: N802\n        name = self.qtile.core.conn.atoms.get_name(e.atom)\n        if name == \"_XEMBED_INFO\":\n            info = self.window.get_property(\"_XEMBED_INFO\", unpack=int)\n            if info and info[1]:\n                self.systray.bar.draw()\n\n        return False\n\n    def handle_DestroyNotify(self, event):  # noqa: N802\n        wid = event.window\n        icon = self.qtile.windows_map.pop(wid)\n        self.systray.tray_icons.remove(icon)\n        self.systray.bar.draw()\n        return False\n\n    handle_UnmapNotify = handle_DestroyNotify  # noqa: N815\n\n\nclass Systray(window._Window, base._Widget):\n    \"\"\"\n    A widget that manages system tray.\n\n    Only one Systray widget is allowed. Adding additional Systray\n    widgets will result in a ConfigError.\n\n    .. note::\n        Icons will not render correctly where the bar/widget is\n        drawn with a semi-transparent background. Instead, icons\n        will be drawn with a transparent background.\n\n        If using this widget it is therefore recommended to use\n        a fully opaque background colour or a fully transparent\n        one.\n    \"\"\"\n\n    _instances = 0\n\n    _window_mask = EventMask.StructureNotify | EventMask.Exposure\n\n    orientations = base.ORIENTATION_BOTH\n\n    defaults = [\n        (\"icon_size\", 20, \"Icon width\"),\n        (\"padding\", 5, \"Padding between icons\"),\n    ]\n\n    def __init__(self, **config):\n        base._Widget.__init__(self, bar.CALCULATED, **config)\n        self.add_defaults(Systray.defaults)\n        self.tray_icons = []\n        self.screen = 0\n        self._name = config.get(\"name\", \"systray\")\n        self._wm_class: Optional[List[str]] = None\n\n    def calculate_length(self):\n        if self.bar.horizontal:\n            length = sum(i.width for i in self.tray_icons)\n        else:\n            length = sum(i.height for i in self.tray_icons)\n        length += self.padding * len(self.tray_icons)\n        return length\n\n    def _configure(self, qtile, bar):\n        base._Widget._configure(self, qtile, bar)\n\n        if Systray._instances > 0:\n            raise ConfigError(\"Only one Systray can be used.\")\n\n        if self.configured:\n            return\n\n        self.conn = conn = qtile.core.conn\n        win = conn.create_window(-1, -1, 1, 1)\n        window._Window.__init__(self, window.XWindow(conn, win.wid), qtile)\n        qtile.windows_map[win.wid] = self\n\n        # window._Window.__init__ overwrites the widget name so we need to restore it\n        self.name = self._name\n\n        # Even when we have multiple \"Screen\"s, we are setting up as the system\n        # tray on a particular X display, that is the screen we need to\n        # reference in the atom\n        if qtile.current_screen:\n            self.screen = qtile.current_screen.index\n        self.bar = bar\n        atoms = conn.atoms\n\n        # We need tray to tell icons which visual to use.\n        # This needs to be the same as the bar/widget.\n        # This mainly benefits transparent bars.\n        conn.conn.core.ChangeProperty(\n            xcffib.xproto.PropMode.Replace,\n            win.wid,\n            atoms[\"_NET_SYSTEM_TRAY_VISUAL\"],\n            xcffib.xproto.Atom.VISUALID,\n            32,\n            1,\n            [self.drawer._visual.visual_id],\n        )\n\n        conn.conn.core.SetSelectionOwner(\n            win.wid, atoms[\"_NET_SYSTEM_TRAY_S{:d}\".format(self.screen)], xcffib.CurrentTime\n        )\n        data = [\n            xcffib.CurrentTime,\n            atoms[\"_NET_SYSTEM_TRAY_S{:d}\".format(self.screen)],\n            win.wid,\n            0,\n            0,\n        ]\n        union = ClientMessageData.synthetic(data, \"I\" * 5)\n        event = ClientMessageEvent.synthetic(\n            format=32, window=qtile.core._root.wid, type=atoms[\"MANAGER\"], data=union\n        )\n        qtile.core._root.send_event(event, mask=EventMask.StructureNotify)\n\n        Systray._instances += 1\n\n    def create_mirror(self):\n        \"\"\"\n        Systray cannot be mirrored as we do not use a Drawer object to render icons.\n\n        Return itself so that, when the bar tries to configure it again, a ConfigError\n        is raised.\n        \"\"\"\n        return self\n\n    def handle_ClientMessage(self, event):  # noqa: N802\n        atoms = self.conn.atoms\n\n        opcode = event.type\n        data = event.data.data32\n        message = data[1]\n        wid = data[2]\n\n        parent = self.bar.window.window\n\n        if opcode == atoms[\"_NET_SYSTEM_TRAY_OPCODE\"] and message == 0:\n            w = window.XWindow(self.conn, wid)\n            icon = Icon(w, self.qtile, self)\n            if icon not in self.tray_icons:\n                self.tray_icons.append(icon)\n                self.tray_icons.sort(key=lambda icon: icon.name)\n                self.qtile.windows_map[wid] = icon\n\n            self.conn.conn.core.ChangeSaveSet(SetMode.Insert, wid)\n            self.conn.conn.core.ReparentWindow(wid, parent.wid, 0, 0)\n            self.conn.conn.flush()\n\n            info = icon.window.get_property(\"_XEMBED_INFO\", unpack=int)\n\n            if not info:\n                self.bar.draw()\n                return False\n\n            if info[1]:\n                self.bar.draw()\n\n        return False\n\n    def draw(self):\n        offset = self.padding\n        self.drawer.clear(self.background or self.bar.background)\n        self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)\n        for pos, icon in enumerate(self.tray_icons):\n            icon.window.set_attribute(backpixmap=self.drawer.pixmap)\n            if self.bar.horizontal:\n                xoffset = self.offsetx + offset\n                yoffset = self.bar.height // 2 - self.icon_size // 2 + self.offsety\n                step = icon.width\n            else:\n                xoffset = self.bar.width // 2 - self.icon_size // 2 + self.offsetx\n                yoffset = self.offsety + offset\n                step = icon.height\n\n            icon.place(xoffset, yoffset, icon.width, self.icon_size, 0, None)\n            if icon.hidden:\n                icon.unhide()\n                data = [\n                    self.conn.atoms[\"_XEMBED_EMBEDDED_NOTIFY\"],\n                    xcffib.xproto.Time.CurrentTime,\n                    0,\n                    self.bar.window.wid,\n                    XEMBED_PROTOCOL_VERSION,\n                ]\n                u = xcffib.xproto.ClientMessageData.synthetic(data, \"I\" * 5)\n                event = xcffib.xproto.ClientMessageEvent.synthetic(\n                    format=32, window=icon.wid, type=self.conn.atoms[\"_XEMBED\"], data=u\n                )\n                self.window.send_event(event)\n\n            offset += step + self.padding\n\n    def finalize(self):\n        base._Widget.finalize(self)\n        atoms = self.conn.atoms\n        self.conn.conn.core.SetSelectionOwner(\n            0,\n            atoms[\"_NET_SYSTEM_TRAY_S{:d}\".format(self.screen)],\n            xcffib.CurrentTime,\n        )\n        self.hide()\n\n        root = self.qtile.core._root.wid\n        for icon in self.tray_icons:\n            self.conn.conn.core.ReparentWindow(icon.window.wid, root, 0, 0)\n        self.conn.conn.flush()\n\n        del self.qtile.windows_map[self.wid]\n        self.conn.conn.core.DestroyWindow(self.wid)\n\n        Systray._instances -= 1\n", "path": "libqtile/widget/systray.py"}]}