We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Wayland LaunchBar not rendering image
### Issue description

Using the LaunchBar widget configured like this: 
```python
widget.LaunchBar(progs=[("/home/silas/Nextcloud/uni/stundenplan.png", "mupdf /home/silas/Nextcloud/uni/stundenplan.png","stundenplan")]),
```
leads to not showing the image but a hole in my bar when using wayland (X11 works fine).

I tested with older versions of this widget. This widget works fine in Wayland before the changes in #3857 by @elParaguayo .

After revoking some changes by this pr it wokred again after replacing https://github.com/qtile/qtile/blob/1e0f09f45a4396d563039bf711ed82bdbeae8b36/libqtile/widget/launchbar.py#L291
with
```python
self.drawer.draw(offsetx=self.offset + xoffset, offsety=self.offsety, width=self.width)
```
Seems to work fine in X11 as well but I'm not sure if this may lead to other problems or if this is an appropriate fix for this bug as the X11 _version_ seems to render this differently than the wayland _version_

### Version

0.23.0

### Backend

Wayland (experimental)

### Config

_No response_

### Logs

_No response_

### Required

- [X] I have searched past issues to see if this bug has already been reported, and it hasn't been.
- [X] I understand that people give their precious time for free, and thus I've done my very best to make this problem as easy as possible to investigate.

--- 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/launchbar.py`
Content:
```
1 # Copyright (c) 2014 Tycho Andersen
2 # Copyright (c) 2014 dequis
3 # Copyright (c) 2014-2015 Joseph Razik
4 # Copyright (c) 2014 Sean Vig
5 # Copyright (c) 2015 reus
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 # SOFTWARE.
24 
25 """
26 This module define a widget that displays icons to launch softwares or commands
27 when clicked -- a launchbar.
28 Only png icon files are displayed, not xpm because cairo doesn't support
29 loading of xpm file.
30 The order of displaying (from left to right) is in the order of the list.
31 
32 If no icon was found for the name provided and if default_icon is set to None
33 then the name is printed instead. If default_icon is defined then this icon is
34 displayed instead.
35 
36 To execute a software:
37  - ('thunderbird', 'thunderbird -safe-mode', 'launch thunderbird in safe mode')
38 To execute a python command in qtile, begin with by 'qshell:'
39  - ('/path/to/icon.png', 'qshell:self.qtile.shutdown()', 'logout from qtile')
40 
41 
42 """
43 from __future__ import annotations
44 
45 import os.path
46 
47 import cairocffi
48 
49 try:
50     from xdg.IconTheme import getIconPath
51 
52     has_xdg = True
53 except ImportError:
54     has_xdg = False
55 
56 from libqtile import bar
57 from libqtile.images import Img
58 from libqtile.log_utils import logger
59 from libqtile.widget import base
60 
61 
62 class LaunchBar(base._Widget):
63     """
64     A widget that display icons to launch the associated command.
65 
66     Text will displayed when no icon is found.
67 
68     Optional requirements: `pyxdg <https://pypi.org/project/pyxdg/>`__ for finding the icon path if it is not provided in the ``progs`` tuple.
69     """
70 
71     orientations = base.ORIENTATION_HORIZONTAL
72     defaults = [
73         ("padding", 2, "Padding between icons"),
74         (
75             "default_icon",
76             "/usr/share/icons/oxygen/256x256/mimetypes/application-x-executable.png",
77             "Default icon not found",
78         ),
79         ("font", "sans", "Text font"),
80         ("fontsize", None, "Font pixel size. Calculated if None."),
81         ("fontshadow", None, "Font shadow color, default is None (no shadow)"),
82         ("foreground", "#ffffff", "Text colour."),
83         (
84             "progs",
85             [],
86             "A list of tuples (software_name or icon_path, command_to_execute, comment), for example:"
87             " [('thunderbird', 'thunderbird -safe-mode', 'launch thunderbird in safe mode'), "
88             " ('/path/to/icon.png', 'qshell:self.qtile.shutdown()', 'logout from qtile')]",
89         ),
90         ("text_only", False, "Don't use any icons."),
91         ("icon_size", None, "Size of icons. ``None`` to fit to bar."),
92         ("padding_y", 0, "Vertical adjustment for icons."),
93         (
94             "theme_path",
95             None,
96             "Path to icon theme to be used by pyxdg for icons. ``None`` will use default icon theme.",
97         ),
98     ]
99 
100     def __init__(
101         self, _progs: list[tuple[str, str, str]] | None = None, width=bar.CALCULATED, **config
102     ):
103         base._Widget.__init__(self, width, **config)
104         self.add_defaults(LaunchBar.defaults)
105         self.surfaces: dict[str, Img | base._TextBox] = {}
106         self.icons_files: dict[str, str | None] = {}
107         self.icons_widths: dict[str, int] = {}
108         self.icons_offsets: dict[str, int] = {}
109 
110         if _progs:
111             logger.warning(
112                 "The use of a positional argument in LaunchBar is deprecated. "
113                 "Please update your config to use progs=[...]."
114             )
115             config["progs"] = _progs
116 
117         # For now, ignore the comments but may be one day it will be useful
118         self.progs = dict(
119             enumerate(
120                 [
121                     {
122                         "name": prog[0],
123                         "cmd": prog[1],
124                         "comment": prog[2] if len(prog) > 2 else None,
125                     }
126                     for prog in config.get("progs", list())
127                 ]
128             )
129         )
130         self.progs_name = set([prog["name"] for prog in self.progs.values()])
131         self.length_type = bar.STATIC
132         self.length = 0
133 
134     def _configure(self, qtile, pbar):
135         base._Widget._configure(self, qtile, pbar)
136         self.lookup_icons()
137         self.setup_images()
138         self.length = self.calculate_length()
139 
140     def setup_images(self):
141         """Create image structures for each icon files."""
142         self._icon_size = self.icon_size if self.icon_size is not None else self.bar.height - 4
143         self._icon_padding = (self.bar.height - self._icon_size) // 2
144 
145         for img_name, iconfile in self.icons_files.items():
146             if iconfile is None or self.text_only:
147                 # Only warn the user that there's no icon if they haven't set text only mode
148                 if not self.text_only:
149                     logger.warning(
150                         'No icon found for application "%s" (%s) switch to text mode',
151                         img_name,
152                         iconfile,
153                     )
154                 # if no icon is found and no default icon was set, we just
155                 # print the name, based on a textbox.
156                 textbox = base._TextBox()
157                 textbox._configure(self.qtile, self.bar)
158                 textbox.layout = self.drawer.textlayout(
159                     textbox.text,
160                     self.foreground,
161                     self.font,
162                     self.fontsize,
163                     self.fontshadow,
164                     markup=textbox.markup,
165                 )
166                 # the name will be displayed
167                 textbox.text = img_name
168                 textbox.calculate_length()
169                 self.icons_widths[img_name] = textbox.width
170                 self.surfaces[img_name] = textbox
171                 continue
172             else:
173                 try:
174                     img = Img.from_path(iconfile)
175                 except cairocffi.Error:
176                     logger.exception(
177                         'Error loading icon for application "%s" (%s)', img_name, iconfile
178                     )
179                     return
180 
181             input_width = img.width
182             input_height = img.height
183 
184             sp = input_height / (self._icon_size)
185             width = int(input_width / sp)
186 
187             imgpat = cairocffi.SurfacePattern(img.surface)
188             scaler = cairocffi.Matrix()
189             scaler.scale(sp, sp)
190             scaler.translate(self.padding * -1, -2)
191             imgpat.set_matrix(scaler)
192 
193             imgpat.set_filter(cairocffi.FILTER_BEST)
194             self.surfaces[img_name] = imgpat
195             self.icons_widths[img_name] = width
196 
197     def _lookup_icon(self, name):
198         """Search for the icon corresponding to one command."""
199         self.icons_files[name] = None
200 
201         # expands ~ if name is a path and does nothing if not
202         ipath = os.path.expanduser(name)
203 
204         # if the software_name is directly an absolute path icon file
205         if os.path.isabs(ipath):
206             # name start with '/' thus it's an absolute path
207             root, ext = os.path.splitext(ipath)
208             img_extensions = [".tif", ".tiff", ".bmp", ".jpg", ".jpeg", ".gif", ".png", ".svg"]
209             if ext in img_extensions:
210                 self.icons_files[name] = ipath if os.path.isfile(ipath) else None
211             else:
212                 # try to add the extension
213                 for extension in img_extensions:
214                     if os.path.isfile(ipath + extension):
215                         self.icons_files[name] = ipath + extension
216                         break
217         elif has_xdg:
218             self.icons_files[name] = getIconPath(name, theme=self.theme_path)
219         # no search method found an icon, so default icon
220         if self.icons_files[name] is None:
221             self.icons_files[name] = self.default_icon
222 
223     def lookup_icons(self):
224         """Search for the icons corresponding to the commands to execute."""
225         if self.default_icon is not None:
226             if not os.path.isfile(self.default_icon):
227                 # if the default icon provided is not found, switch to
228                 # text mode
229                 self.default_icon = None
230         for name in self.progs_name:
231             self._lookup_icon(name)
232 
233     def get_icon_in_position(self, x, y):
234         """Determine which icon is clicked according to its position."""
235         for i in self.progs:
236             if x < (
237                 self.icons_offsets[i]
238                 + self.icons_widths[self.progs[i]["name"]]
239                 + self.padding / 2
240             ):
241                 return i
242 
243     def button_press(self, x, y, button):
244         """Launch the associated command to the clicked icon."""
245         base._Widget.button_press(self, x, y, button)
246         if button == 1:
247             icon = self.get_icon_in_position(x, y)
248             if icon is not None:
249                 cmd = self.progs[icon]["cmd"]
250                 if cmd.startswith("qshell:"):
251                     exec(cmd[7:].lstrip())
252                 else:
253                     self.qtile.spawn(cmd)
254             self.draw()
255 
256     def draw(self):
257         """Draw the icons in the widget."""
258         self.drawer.clear(self.background or self.bar.background)
259         xoffset = 0
260         for i in sorted(self.progs.keys()):
261             self.drawer.ctx.save()
262             self.drawer.ctx.translate(xoffset, 0)
263             self.icons_offsets[i] = xoffset + self.padding
264             name = self.progs[i]["name"]
265             icon_width = self.icons_widths[name]
266             if isinstance(self.surfaces[name], base._TextBox):
267                 # display the name if no icon was found and no default icon
268                 textbox = self.surfaces[name]
269                 textbox.layout.draw(
270                     self.padding + textbox.actual_padding,
271                     int((self.bar.height - textbox.layout.height) / 2.0) + 1,
272                 )
273             else:
274                 # display an icon
275                 self.drawer.ctx.save()
276                 self.drawer.ctx.translate(0, self._icon_padding + self.padding_y)
277                 self.drawer.ctx.set_source(self.surfaces[name])
278                 self.drawer.ctx.paint()
279                 self.drawer.ctx.restore()
280 
281             self.drawer.ctx.restore()
282 
283             self.drawer.draw(
284                 offsetx=self.offset + xoffset,
285                 offsety=self.offsety,
286                 width=icon_width + self.padding,
287             )
288 
289             xoffset += icon_width + self.padding
290 
291         self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.width)
292 
293     def calculate_length(self):
294         """Compute the width of the widget according to each icon width."""
295         return sum(
296             self.icons_widths[prg["name"]] for prg in self.progs.values()
297         ) + self.padding * (len(self.progs) + 1)
298
```
--- 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/launchbar.py b/libqtile/widget/launchbar.py
--- a/libqtile/widget/launchbar.py
+++ b/libqtile/widget/launchbar.py
@@ -272,20 +272,13 @@
                 )
             else:
                 # display an icon
-                self.drawer.ctx.save()
+                # Translate to vertically centre the icon
                 self.drawer.ctx.translate(0, self._icon_padding + self.padding_y)
                 self.drawer.ctx.set_source(self.surfaces[name])
                 self.drawer.ctx.paint()
-                self.drawer.ctx.restore()
 
             self.drawer.ctx.restore()
 
-            self.drawer.draw(
-                offsetx=self.offset + xoffset,
-                offsety=self.offsety,
-                width=icon_width + self.padding,
-            )
-
             xoffset += icon_width + self.padding
 
         self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.width)

{"golden_diff": "diff --git a/libqtile/widget/launchbar.py b/libqtile/widget/launchbar.py\n--- a/libqtile/widget/launchbar.py\n+++ b/libqtile/widget/launchbar.py\n@@ -272,20 +272,13 @@\n                 )\n             else:\n                 # display an icon\n-                self.drawer.ctx.save()\n+                # Translate to vertically centre the icon\n                 self.drawer.ctx.translate(0, self._icon_padding + self.padding_y)\n                 self.drawer.ctx.set_source(self.surfaces[name])\n                 self.drawer.ctx.paint()\n-                self.drawer.ctx.restore()\n \n             self.drawer.ctx.restore()\n \n-            self.drawer.draw(\n-                offsetx=self.offset + xoffset,\n-                offsety=self.offsety,\n-                width=icon_width + self.padding,\n-            )\n-\n             xoffset += icon_width + self.padding\n \n         self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.width)\n", "issue": "Wayland LaunchBar not rendering image\n### Issue description\n\nUsing the LaunchBar widget configured like this: \r\n```python\r\nwidget.LaunchBar(progs=[(\"/home/silas/Nextcloud/uni/stundenplan.png\", \"mupdf /home/silas/Nextcloud/uni/stundenplan.png\",\"stundenplan\")]),\r\n```\r\nleads to not showing the image but a hole in my bar when using wayland (X11 works fine).\r\n\r\nI tested with older versions of this widget. This widget works fine in Wayland before the changes in #3857 by @elParaguayo .\r\n\r\nAfter revoking some changes by this pr it wokred again after replacing https://github.com/qtile/qtile/blob/1e0f09f45a4396d563039bf711ed82bdbeae8b36/libqtile/widget/launchbar.py#L291\r\nwith\r\n```python\r\nself.drawer.draw(offsetx=self.offset + xoffset, offsety=self.offsety, width=self.width)\r\n```\r\nSeems to work fine in X11 as well but I'm not sure if this may lead to other problems or if this is an appropriate fix for this bug as the X11 _version_ seems to render this differently than the wayland _version_\n\n### Version\n\n0.23.0\n\n### Backend\n\nWayland (experimental)\n\n### Config\n\n_No response_\n\n### Logs\n\n_No response_\n\n### Required\n\n- [X] I have searched past issues to see if this bug has already been reported, and it hasn't been.\n- [X] I understand that people give their precious time for free, and thus I've done my very best to make this problem as easy as possible to investigate.\n", "before_files": [{"content": "# Copyright (c) 2014 Tycho Andersen\n# Copyright (c) 2014 dequis\n# Copyright (c) 2014-2015 Joseph Razik\n# Copyright (c) 2014 Sean Vig\n# Copyright (c) 2015 reus\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.\n\n\"\"\"\nThis module define a widget that displays icons to launch softwares or commands\nwhen clicked -- a launchbar.\nOnly png icon files are displayed, not xpm because cairo doesn't support\nloading of xpm file.\nThe order of displaying (from left to right) is in the order of the list.\n\nIf no icon was found for the name provided and if default_icon is set to None\nthen the name is printed instead. If default_icon is defined then this icon is\ndisplayed instead.\n\nTo execute a software:\n - ('thunderbird', 'thunderbird -safe-mode', 'launch thunderbird in safe mode')\nTo execute a python command in qtile, begin with by 'qshell:'\n - ('/path/to/icon.png', 'qshell:self.qtile.shutdown()', 'logout from qtile')\n\n\n\"\"\"\nfrom __future__ import annotations\n\nimport os.path\n\nimport cairocffi\n\ntry:\n    from xdg.IconTheme import getIconPath\n\n    has_xdg = True\nexcept ImportError:\n    has_xdg = False\n\nfrom libqtile import bar\nfrom libqtile.images import Img\nfrom libqtile.log_utils import logger\nfrom libqtile.widget import base\n\n\nclass LaunchBar(base._Widget):\n    \"\"\"\n    A widget that display icons to launch the associated command.\n\n    Text will displayed when no icon is found.\n\n    Optional requirements: `pyxdg <https://pypi.org/project/pyxdg/>`__ for finding the icon path if it is not provided in the ``progs`` tuple.\n    \"\"\"\n\n    orientations = base.ORIENTATION_HORIZONTAL\n    defaults = [\n        (\"padding\", 2, \"Padding between icons\"),\n        (\n            \"default_icon\",\n            \"/usr/share/icons/oxygen/256x256/mimetypes/application-x-executable.png\",\n            \"Default icon not found\",\n        ),\n        (\"font\", \"sans\", \"Text font\"),\n        (\"fontsize\", None, \"Font pixel size. Calculated if None.\"),\n        (\"fontshadow\", None, \"Font shadow color, default is None (no shadow)\"),\n        (\"foreground\", \"#ffffff\", \"Text colour.\"),\n        (\n            \"progs\",\n            [],\n            \"A list of tuples (software_name or icon_path, command_to_execute, comment), for example:\"\n            \" [('thunderbird', 'thunderbird -safe-mode', 'launch thunderbird in safe mode'), \"\n            \" ('/path/to/icon.png', 'qshell:self.qtile.shutdown()', 'logout from qtile')]\",\n        ),\n        (\"text_only\", False, \"Don't use any icons.\"),\n        (\"icon_size\", None, \"Size of icons. ``None`` to fit to bar.\"),\n        (\"padding_y\", 0, \"Vertical adjustment for icons.\"),\n        (\n            \"theme_path\",\n            None,\n            \"Path to icon theme to be used by pyxdg for icons. ``None`` will use default icon theme.\",\n        ),\n    ]\n\n    def __init__(\n        self, _progs: list[tuple[str, str, str]] | None = None, width=bar.CALCULATED, **config\n    ):\n        base._Widget.__init__(self, width, **config)\n        self.add_defaults(LaunchBar.defaults)\n        self.surfaces: dict[str, Img | base._TextBox] = {}\n        self.icons_files: dict[str, str | None] = {}\n        self.icons_widths: dict[str, int] = {}\n        self.icons_offsets: dict[str, int] = {}\n\n        if _progs:\n            logger.warning(\n                \"The use of a positional argument in LaunchBar is deprecated. \"\n                \"Please update your config to use progs=[...].\"\n            )\n            config[\"progs\"] = _progs\n\n        # For now, ignore the comments but may be one day it will be useful\n        self.progs = dict(\n            enumerate(\n                [\n                    {\n                        \"name\": prog[0],\n                        \"cmd\": prog[1],\n                        \"comment\": prog[2] if len(prog) > 2 else None,\n                    }\n                    for prog in config.get(\"progs\", list())\n                ]\n            )\n        )\n        self.progs_name = set([prog[\"name\"] for prog in self.progs.values()])\n        self.length_type = bar.STATIC\n        self.length = 0\n\n    def _configure(self, qtile, pbar):\n        base._Widget._configure(self, qtile, pbar)\n        self.lookup_icons()\n        self.setup_images()\n        self.length = self.calculate_length()\n\n    def setup_images(self):\n        \"\"\"Create image structures for each icon files.\"\"\"\n        self._icon_size = self.icon_size if self.icon_size is not None else self.bar.height - 4\n        self._icon_padding = (self.bar.height - self._icon_size) // 2\n\n        for img_name, iconfile in self.icons_files.items():\n            if iconfile is None or self.text_only:\n                # Only warn the user that there's no icon if they haven't set text only mode\n                if not self.text_only:\n                    logger.warning(\n                        'No icon found for application \"%s\" (%s) switch to text mode',\n                        img_name,\n                        iconfile,\n                    )\n                # if no icon is found and no default icon was set, we just\n                # print the name, based on a textbox.\n                textbox = base._TextBox()\n                textbox._configure(self.qtile, self.bar)\n                textbox.layout = self.drawer.textlayout(\n                    textbox.text,\n                    self.foreground,\n                    self.font,\n                    self.fontsize,\n                    self.fontshadow,\n                    markup=textbox.markup,\n                )\n                # the name will be displayed\n                textbox.text = img_name\n                textbox.calculate_length()\n                self.icons_widths[img_name] = textbox.width\n                self.surfaces[img_name] = textbox\n                continue\n            else:\n                try:\n                    img = Img.from_path(iconfile)\n                except cairocffi.Error:\n                    logger.exception(\n                        'Error loading icon for application \"%s\" (%s)', img_name, iconfile\n                    )\n                    return\n\n            input_width = img.width\n            input_height = img.height\n\n            sp = input_height / (self._icon_size)\n            width = int(input_width / sp)\n\n            imgpat = cairocffi.SurfacePattern(img.surface)\n            scaler = cairocffi.Matrix()\n            scaler.scale(sp, sp)\n            scaler.translate(self.padding * -1, -2)\n            imgpat.set_matrix(scaler)\n\n            imgpat.set_filter(cairocffi.FILTER_BEST)\n            self.surfaces[img_name] = imgpat\n            self.icons_widths[img_name] = width\n\n    def _lookup_icon(self, name):\n        \"\"\"Search for the icon corresponding to one command.\"\"\"\n        self.icons_files[name] = None\n\n        # expands ~ if name is a path and does nothing if not\n        ipath = os.path.expanduser(name)\n\n        # if the software_name is directly an absolute path icon file\n        if os.path.isabs(ipath):\n            # name start with '/' thus it's an absolute path\n            root, ext = os.path.splitext(ipath)\n            img_extensions = [\".tif\", \".tiff\", \".bmp\", \".jpg\", \".jpeg\", \".gif\", \".png\", \".svg\"]\n            if ext in img_extensions:\n                self.icons_files[name] = ipath if os.path.isfile(ipath) else None\n            else:\n                # try to add the extension\n                for extension in img_extensions:\n                    if os.path.isfile(ipath + extension):\n                        self.icons_files[name] = ipath + extension\n                        break\n        elif has_xdg:\n            self.icons_files[name] = getIconPath(name, theme=self.theme_path)\n        # no search method found an icon, so default icon\n        if self.icons_files[name] is None:\n            self.icons_files[name] = self.default_icon\n\n    def lookup_icons(self):\n        \"\"\"Search for the icons corresponding to the commands to execute.\"\"\"\n        if self.default_icon is not None:\n            if not os.path.isfile(self.default_icon):\n                # if the default icon provided is not found, switch to\n                # text mode\n                self.default_icon = None\n        for name in self.progs_name:\n            self._lookup_icon(name)\n\n    def get_icon_in_position(self, x, y):\n        \"\"\"Determine which icon is clicked according to its position.\"\"\"\n        for i in self.progs:\n            if x < (\n                self.icons_offsets[i]\n                + self.icons_widths[self.progs[i][\"name\"]]\n                + self.padding / 2\n            ):\n                return i\n\n    def button_press(self, x, y, button):\n        \"\"\"Launch the associated command to the clicked icon.\"\"\"\n        base._Widget.button_press(self, x, y, button)\n        if button == 1:\n            icon = self.get_icon_in_position(x, y)\n            if icon is not None:\n                cmd = self.progs[icon][\"cmd\"]\n                if cmd.startswith(\"qshell:\"):\n                    exec(cmd[7:].lstrip())\n                else:\n                    self.qtile.spawn(cmd)\n            self.draw()\n\n    def draw(self):\n        \"\"\"Draw the icons in the widget.\"\"\"\n        self.drawer.clear(self.background or self.bar.background)\n        xoffset = 0\n        for i in sorted(self.progs.keys()):\n            self.drawer.ctx.save()\n            self.drawer.ctx.translate(xoffset, 0)\n            self.icons_offsets[i] = xoffset + self.padding\n            name = self.progs[i][\"name\"]\n            icon_width = self.icons_widths[name]\n            if isinstance(self.surfaces[name], base._TextBox):\n                # display the name if no icon was found and no default icon\n                textbox = self.surfaces[name]\n                textbox.layout.draw(\n                    self.padding + textbox.actual_padding,\n                    int((self.bar.height - textbox.layout.height) / 2.0) + 1,\n                )\n            else:\n                # display an icon\n                self.drawer.ctx.save()\n                self.drawer.ctx.translate(0, self._icon_padding + self.padding_y)\n                self.drawer.ctx.set_source(self.surfaces[name])\n                self.drawer.ctx.paint()\n                self.drawer.ctx.restore()\n\n            self.drawer.ctx.restore()\n\n            self.drawer.draw(\n                offsetx=self.offset + xoffset,\n                offsety=self.offsety,\n                width=icon_width + self.padding,\n            )\n\n            xoffset += icon_width + self.padding\n\n        self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.width)\n\n    def calculate_length(self):\n        \"\"\"Compute the width of the widget according to each icon width.\"\"\"\n        return sum(\n            self.icons_widths[prg[\"name\"]] for prg in self.progs.values()\n        ) + self.padding * (len(self.progs) + 1)\n", "path": "libqtile/widget/launchbar.py"}], "after_files": [{"content": "# Copyright (c) 2014 Tycho Andersen\n# Copyright (c) 2014 dequis\n# Copyright (c) 2014-2015 Joseph Razik\n# Copyright (c) 2014 Sean Vig\n# Copyright (c) 2015 reus\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.\n\n\"\"\"\nThis module define a widget that displays icons to launch softwares or commands\nwhen clicked -- a launchbar.\nOnly png icon files are displayed, not xpm because cairo doesn't support\nloading of xpm file.\nThe order of displaying (from left to right) is in the order of the list.\n\nIf no icon was found for the name provided and if default_icon is set to None\nthen the name is printed instead. If default_icon is defined then this icon is\ndisplayed instead.\n\nTo execute a software:\n - ('thunderbird', 'thunderbird -safe-mode', 'launch thunderbird in safe mode')\nTo execute a python command in qtile, begin with by 'qshell:'\n - ('/path/to/icon.png', 'qshell:self.qtile.shutdown()', 'logout from qtile')\n\n\n\"\"\"\nfrom __future__ import annotations\n\nimport os.path\n\nimport cairocffi\n\ntry:\n    from xdg.IconTheme import getIconPath\n\n    has_xdg = True\nexcept ImportError:\n    has_xdg = False\n\nfrom libqtile import bar\nfrom libqtile.images import Img\nfrom libqtile.log_utils import logger\nfrom libqtile.widget import base\n\n\nclass LaunchBar(base._Widget):\n    \"\"\"\n    A widget that display icons to launch the associated command.\n\n    Text will displayed when no icon is found.\n\n    Optional requirements: `pyxdg <https://pypi.org/project/pyxdg/>`__ for finding the icon path if it is not provided in the ``progs`` tuple.\n    \"\"\"\n\n    orientations = base.ORIENTATION_HORIZONTAL\n    defaults = [\n        (\"padding\", 2, \"Padding between icons\"),\n        (\n            \"default_icon\",\n            \"/usr/share/icons/oxygen/256x256/mimetypes/application-x-executable.png\",\n            \"Default icon not found\",\n        ),\n        (\"font\", \"sans\", \"Text font\"),\n        (\"fontsize\", None, \"Font pixel size. Calculated if None.\"),\n        (\"fontshadow\", None, \"Font shadow color, default is None (no shadow)\"),\n        (\"foreground\", \"#ffffff\", \"Text colour.\"),\n        (\n            \"progs\",\n            [],\n            \"A list of tuples (software_name or icon_path, command_to_execute, comment), for example:\"\n            \" [('thunderbird', 'thunderbird -safe-mode', 'launch thunderbird in safe mode'), \"\n            \" ('/path/to/icon.png', 'qshell:self.qtile.shutdown()', 'logout from qtile')]\",\n        ),\n        (\"text_only\", False, \"Don't use any icons.\"),\n        (\"icon_size\", None, \"Size of icons. ``None`` to fit to bar.\"),\n        (\"padding_y\", 0, \"Vertical adjustment for icons.\"),\n        (\n            \"theme_path\",\n            None,\n            \"Path to icon theme to be used by pyxdg for icons. ``None`` will use default icon theme.\",\n        ),\n    ]\n\n    def __init__(\n        self, _progs: list[tuple[str, str, str]] | None = None, width=bar.CALCULATED, **config\n    ):\n        base._Widget.__init__(self, width, **config)\n        self.add_defaults(LaunchBar.defaults)\n        self.surfaces: dict[str, Img | base._TextBox] = {}\n        self.icons_files: dict[str, str | None] = {}\n        self.icons_widths: dict[str, int] = {}\n        self.icons_offsets: dict[str, int] = {}\n\n        if _progs:\n            logger.warning(\n                \"The use of a positional argument in LaunchBar is deprecated. \"\n                \"Please update your config to use progs=[...].\"\n            )\n            config[\"progs\"] = _progs\n\n        # For now, ignore the comments but may be one day it will be useful\n        self.progs = dict(\n            enumerate(\n                [\n                    {\n                        \"name\": prog[0],\n                        \"cmd\": prog[1],\n                        \"comment\": prog[2] if len(prog) > 2 else None,\n                    }\n                    for prog in config.get(\"progs\", list())\n                ]\n            )\n        )\n        self.progs_name = set([prog[\"name\"] for prog in self.progs.values()])\n        self.length_type = bar.STATIC\n        self.length = 0\n\n    def _configure(self, qtile, pbar):\n        base._Widget._configure(self, qtile, pbar)\n        self.lookup_icons()\n        self.setup_images()\n        self.length = self.calculate_length()\n\n    def setup_images(self):\n        \"\"\"Create image structures for each icon files.\"\"\"\n        self._icon_size = self.icon_size if self.icon_size is not None else self.bar.height - 4\n        self._icon_padding = (self.bar.height - self._icon_size) // 2\n\n        for img_name, iconfile in self.icons_files.items():\n            if iconfile is None or self.text_only:\n                # Only warn the user that there's no icon if they haven't set text only mode\n                if not self.text_only:\n                    logger.warning(\n                        'No icon found for application \"%s\" (%s) switch to text mode',\n                        img_name,\n                        iconfile,\n                    )\n                # if no icon is found and no default icon was set, we just\n                # print the name, based on a textbox.\n                textbox = base._TextBox()\n                textbox._configure(self.qtile, self.bar)\n                textbox.layout = self.drawer.textlayout(\n                    textbox.text,\n                    self.foreground,\n                    self.font,\n                    self.fontsize,\n                    self.fontshadow,\n                    markup=textbox.markup,\n                )\n                # the name will be displayed\n                textbox.text = img_name\n                textbox.calculate_length()\n                self.icons_widths[img_name] = textbox.width\n                self.surfaces[img_name] = textbox\n                continue\n            else:\n                try:\n                    img = Img.from_path(iconfile)\n                except cairocffi.Error:\n                    logger.exception(\n                        'Error loading icon for application \"%s\" (%s)', img_name, iconfile\n                    )\n                    return\n\n            input_width = img.width\n            input_height = img.height\n\n            sp = input_height / (self._icon_size)\n            width = int(input_width / sp)\n\n            imgpat = cairocffi.SurfacePattern(img.surface)\n            scaler = cairocffi.Matrix()\n            scaler.scale(sp, sp)\n            scaler.translate(self.padding * -1, -2)\n            imgpat.set_matrix(scaler)\n\n            imgpat.set_filter(cairocffi.FILTER_BEST)\n            self.surfaces[img_name] = imgpat\n            self.icons_widths[img_name] = width\n\n    def _lookup_icon(self, name):\n        \"\"\"Search for the icon corresponding to one command.\"\"\"\n        self.icons_files[name] = None\n\n        # expands ~ if name is a path and does nothing if not\n        ipath = os.path.expanduser(name)\n\n        # if the software_name is directly an absolute path icon file\n        if os.path.isabs(ipath):\n            # name start with '/' thus it's an absolute path\n            root, ext = os.path.splitext(ipath)\n            img_extensions = [\".tif\", \".tiff\", \".bmp\", \".jpg\", \".jpeg\", \".gif\", \".png\", \".svg\"]\n            if ext in img_extensions:\n                self.icons_files[name] = ipath if os.path.isfile(ipath) else None\n            else:\n                # try to add the extension\n                for extension in img_extensions:\n                    if os.path.isfile(ipath + extension):\n                        self.icons_files[name] = ipath + extension\n                        break\n        elif has_xdg:\n            self.icons_files[name] = getIconPath(name, theme=self.theme_path)\n        # no search method found an icon, so default icon\n        if self.icons_files[name] is None:\n            self.icons_files[name] = self.default_icon\n\n    def lookup_icons(self):\n        \"\"\"Search for the icons corresponding to the commands to execute.\"\"\"\n        if self.default_icon is not None:\n            if not os.path.isfile(self.default_icon):\n                # if the default icon provided is not found, switch to\n                # text mode\n                self.default_icon = None\n        for name in self.progs_name:\n            self._lookup_icon(name)\n\n    def get_icon_in_position(self, x, y):\n        \"\"\"Determine which icon is clicked according to its position.\"\"\"\n        for i in self.progs:\n            if x < (\n                self.icons_offsets[i]\n                + self.icons_widths[self.progs[i][\"name\"]]\n                + self.padding / 2\n            ):\n                return i\n\n    def button_press(self, x, y, button):\n        \"\"\"Launch the associated command to the clicked icon.\"\"\"\n        base._Widget.button_press(self, x, y, button)\n        if button == 1:\n            icon = self.get_icon_in_position(x, y)\n            if icon is not None:\n                cmd = self.progs[icon][\"cmd\"]\n                if cmd.startswith(\"qshell:\"):\n                    exec(cmd[7:].lstrip())\n                else:\n                    self.qtile.spawn(cmd)\n            self.draw()\n\n    def draw(self):\n        \"\"\"Draw the icons in the widget.\"\"\"\n        self.drawer.clear(self.background or self.bar.background)\n        xoffset = 0\n        for i in sorted(self.progs.keys()):\n            self.drawer.ctx.save()\n            self.drawer.ctx.translate(xoffset, 0)\n            self.icons_offsets[i] = xoffset + self.padding\n            name = self.progs[i][\"name\"]\n            icon_width = self.icons_widths[name]\n            if isinstance(self.surfaces[name], base._TextBox):\n                # display the name if no icon was found and no default icon\n                textbox = self.surfaces[name]\n                textbox.layout.draw(\n                    self.padding + textbox.actual_padding,\n                    int((self.bar.height - textbox.layout.height) / 2.0) + 1,\n                )\n            else:\n                # display an icon\n                # Translate to vertically centre the icon\n                self.drawer.ctx.translate(0, self._icon_padding + self.padding_y)\n                self.drawer.ctx.set_source(self.surfaces[name])\n                self.drawer.ctx.paint()\n\n            self.drawer.ctx.restore()\n\n            xoffset += icon_width + self.padding\n\n        self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.width)\n\n    def calculate_length(self):\n        \"\"\"Compute the width of the widget according to each icon width.\"\"\"\n        return sum(\n            self.icons_widths[prg[\"name\"]] for prg in self.progs.values()\n        ) + self.padding * (len(self.progs) + 1)\n", "path": "libqtile/widget/launchbar.py"}]}