We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
CurrentLayoutIcon widget does not load icons for group-specific layouts
### Issue description

When using the `CurrentLayoutIcon` widget with layouts that are defined group-only,
(For example: 
```py
layouts = [
    # Some layouts here
]
groups = [
    Group("0", layouts=[
        # Any layouts not included in `layouts` above
    ])
]
```
)
the icon does not properly load and display for layouts that are only defined for the group specifically.

This issue occurs because in the setup function for loading icons https://github.com/qtile/qtile/blob/fd93e74c110f23846af5f721195b6c1f90b873f8/libqtile/widget/currentlayout.py#L207-L235
 it only loads icons for layouts found by `_get_layout_names`, which only includes layouts defined under `layouts` in the main configuration https://github.com/qtile/qtile/blob/fd93e74c110f23846af5f721195b6c1f90b873f8/libqtile/widget/currentlayout.py#L174-L181

This means that layouts are accessible that do not have icons that are loaded, leading to errors in the form
```
2023-09-24 23:31:50,483 ERROR libqtile currentlayout.py:draw():L158 No icon for layout <layout name>
```
Indicating that the issue isn't that an icon file wasn't found (I have them present in `~/.icons`, and no error message saying they aren't found), but rather that an icon file was never loaded.

A potential solution would be to also go through each group in `_get_layout_names` to also include those layouts, deduplicating as necessary.

### Version

0.22.1

### Backend

X11 (default)

### 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/currentlayout.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2011 Florian Mounier
3 # Copyright (c) 2011 Kenji_Takahashi
4 # Copyright (c) 2012 roger
5 # Copyright (c) 2012, 2014 Tycho Andersen
6 # Copyright (c) 2012 Maximilian Köhl
7 # Copyright (c) 2013 Craig Barnes
8 # Copyright (c) 2014 Sean Vig
9 # Copyright (c) 2014 Adi Sieker
10 #
11 # Permission is hereby granted, free of charge, to any person obtaining a copy
12 # of this software and associated documentation files (the "Software"), to deal
13 # in the Software without restriction, including without limitation the rights
14 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 # copies of the Software, and to permit persons to whom the Software is
16 # furnished to do so, subject to the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included in
19 # all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 # SOFTWARE.
28 import os
29 
30 from libqtile import bar, hook
31 from libqtile.images import Img
32 from libqtile.log_utils import logger
33 from libqtile.widget import base
34 
35 
36 class CurrentLayout(base._TextBox):
37     """
38     Display the name of the current layout of the current group of the screen,
39     the bar containing the widget, is on.
40     """
41 
42     def __init__(self, width=bar.CALCULATED, **config):
43         base._TextBox.__init__(self, "", width, **config)
44 
45     def _configure(self, qtile, bar):
46         base._TextBox._configure(self, qtile, bar)
47         layout_id = self.bar.screen.group.current_layout
48         self.text = self.bar.screen.group.layouts[layout_id].name
49         self.setup_hooks()
50 
51         self.add_callbacks(
52             {
53                 "Button1": qtile.next_layout,
54                 "Button2": qtile.prev_layout,
55             }
56         )
57 
58     def hook_response(self, layout, group):
59         if group.screen is not None and group.screen == self.bar.screen:
60             self.text = layout.name
61             self.bar.draw()
62 
63     def setup_hooks(self):
64         hook.subscribe.layout_change(self.hook_response)
65 
66     def remove_hooks(self):
67         hook.unsubscribe.layout_change(self.hook_response)
68 
69     def finalize(self):
70         self.remove_hooks()
71         base._TextBox.finalize(self)
72 
73 
74 class CurrentLayoutIcon(base._TextBox):
75     """
76     Display the icon representing the current layout of the
77     current group of the screen on which the bar containing the widget is.
78 
79     If you are using custom layouts, a default icon with question mark
80     will be displayed for them. If you want to use custom icon for your own
81     layout, for example, `FooGrid`, then create a file named
82     "layout-foogrid.png" and place it in `~/.icons` directory. You can as well
83     use other directories, but then you need to specify those directories
84     in `custom_icon_paths` argument for this plugin.
85 
86     The widget will look for icons with a `png` or `svg` extension.
87 
88     The order of icon search is:
89 
90     - dirs in `custom_icon_paths` config argument
91     - `~/.icons`
92     - built-in qtile icons
93     """
94 
95     orientations = base.ORIENTATION_HORIZONTAL
96 
97     defaults = [
98         ("scale", 1, "Scale factor relative to the bar height. Defaults to 1"),
99         (
100             "custom_icon_paths",
101             [],
102             "List of folders where to search icons before"
103             "using built-in icons or icons in ~/.icons dir.  "
104             "This can also be used to provide"
105             "missing icons for custom layouts.  "
106             "Defaults to empty list.",
107         ),
108     ]
109 
110     def __init__(self, **config):
111         base._TextBox.__init__(self, "", **config)
112         self.add_defaults(CurrentLayoutIcon.defaults)
113 
114         self.length_type = bar.STATIC
115         self.length = 0
116 
117     def _configure(self, qtile, bar):
118         base._TextBox._configure(self, qtile, bar)
119         layout_id = self.bar.screen.group.current_layout
120         self.text = self.bar.screen.group.layouts[layout_id].name
121         self.current_layout = self.text
122         self.icons_loaded = False
123         self.icon_paths = []
124         self.surfaces = {}
125         self._update_icon_paths()
126         self._setup_images()
127         self._setup_hooks()
128 
129         self.add_callbacks(
130             {
131                 "Button1": qtile.next_layout,
132                 "Button2": qtile.prev_layout,
133             }
134         )
135 
136     def hook_response(self, layout, group):
137         if group.screen is not None and group.screen == self.bar.screen:
138             self.current_layout = layout.name
139             self.bar.draw()
140 
141     def _setup_hooks(self):
142         """
143         Listens for layout change and performs a redraw when it occurs.
144         """
145         hook.subscribe.layout_change(self.hook_response)
146 
147     def _remove_hooks(self):
148         """
149         Listens for layout change and performs a redraw when it occurs.
150         """
151         hook.unsubscribe.layout_change(self.hook_response)
152 
153     def draw(self):
154         if self.icons_loaded:
155             try:
156                 surface = self.surfaces[self.current_layout]
157             except KeyError:
158                 logger.error("No icon for layout %s", self.current_layout)
159             else:
160                 self.drawer.clear(self.background or self.bar.background)
161                 self.drawer.ctx.save()
162                 self.drawer.ctx.translate(
163                     (self.width - surface.width) / 2, (self.bar.height - surface.height) / 2
164                 )
165                 self.drawer.ctx.set_source(surface.pattern)
166                 self.drawer.ctx.paint()
167                 self.drawer.ctx.restore()
168                 self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)
169         else:
170             # Fallback to text
171             self.text = self.current_layout[0].upper()
172             base._TextBox.draw(self)
173 
174     def _get_layout_names(self):
175         """
176         Returns a list of tuples of lowercased layout name and class name strings for each available layout.
177         """
178         return [
179             (layout.name, layout.__class__.__name__.lower())
180             for layout in self.qtile.config.layouts
181         ]
182 
183     def _update_icon_paths(self):
184         self.icon_paths = []
185 
186         # We allow user to override icon search path
187         self.icon_paths.extend(os.path.expanduser(path) for path in self.custom_icon_paths)
188 
189         # We also look in ~/.icons/ and ~/.local/share/icons
190         self.icon_paths.append(os.path.expanduser("~/.icons"))
191         self.icon_paths.append(os.path.expanduser("~/.local/share/icons"))
192 
193         # Default icons are in libqtile/resources/layout-icons.
194         # If using default config without any custom icons,
195         # this path will be used.
196         root = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-2])
197         self.icon_paths.append(os.path.join(root, "resources", "layout-icons"))
198 
199     def find_icon_file_path(self, layout_name):
200         for icon_path in self.icon_paths:
201             for extension in ["png", "svg"]:
202                 icon_filename = "layout-{}.{}".format(layout_name, extension)
203                 icon_file_path = os.path.join(icon_path, icon_filename)
204                 if os.path.isfile(icon_file_path):
205                     return icon_file_path
206 
207     def _setup_images(self):
208         """
209         Loads layout icons.
210         """
211         for names in self._get_layout_names():
212             layout_name = names[0]
213             # Python doesn't have an ordered set but we can use a dictionary instead
214             # First key is the layout's name (which may have been set by the user),
215             # the second is the class name. If these are the same (i.e. the user hasn't
216             # set a name) then there is only one key in the dictionary.
217             layouts = dict.fromkeys(names)
218             for layout in layouts.keys():
219                 icon_file_path = self.find_icon_file_path(layout)
220                 if icon_file_path:
221                     break
222             else:
223                 logger.warning('No icon found for layout "%s"', layout_name)
224                 icon_file_path = self.find_icon_file_path("unknown")
225 
226             img = Img.from_path(icon_file_path)
227 
228             new_height = (self.bar.height - 2) * self.scale
229             img.resize(height=new_height)
230             if img.width > self.length:
231                 self.length = img.width + self.actual_padding * 2
232 
233             self.surfaces[layout_name] = img
234 
235         self.icons_loaded = True
236 
237     def finalize(self):
238         self._remove_hooks()
239         base._TextBox.finalize(self)
240
```
--- 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/currentlayout.py b/libqtile/widget/currentlayout.py
--- a/libqtile/widget/currentlayout.py
+++ b/libqtile/widget/currentlayout.py
@@ -25,6 +25,7 @@
 # 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 itertools
 import os
 
 from libqtile import bar, hook
@@ -160,7 +161,8 @@
                 self.drawer.clear(self.background or self.bar.background)
                 self.drawer.ctx.save()
                 self.drawer.ctx.translate(
-                    (self.width - surface.width) / 2, (self.bar.height - surface.height) / 2
+                    (self.width - surface.width) / 2,
+                    (self.bar.height - surface.height) / 2,
                 )
                 self.drawer.ctx.set_source(surface.pattern)
                 self.drawer.ctx.paint()
@@ -173,12 +175,16 @@
 
     def _get_layout_names(self):
         """
-        Returns a list of tuples of lowercased layout name and class name strings for each available layout.
+        Returns a sequence of tuples of layout name and lowercased class name
+        strings for each available layout.
         """
-        return [
-            (layout.name, layout.__class__.__name__.lower())
-            for layout in self.qtile.config.layouts
-        ]
+
+        layouts = itertools.chain(
+            self.qtile.config.layouts,
+            (layout for group in self.qtile.config.groups for layout in group.layouts),
+        )
+
+        return set((layout.name, layout.__class__.__name__.lower()) for layout in layouts)
 
     def _update_icon_paths(self):
         self.icon_paths = []

{"golden_diff": "diff --git a/libqtile/widget/currentlayout.py b/libqtile/widget/currentlayout.py\n--- a/libqtile/widget/currentlayout.py\n+++ b/libqtile/widget/currentlayout.py\n@@ -25,6 +25,7 @@\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 itertools\n import os\n \n from libqtile import bar, hook\n@@ -160,7 +161,8 @@\n                 self.drawer.clear(self.background or self.bar.background)\n                 self.drawer.ctx.save()\n                 self.drawer.ctx.translate(\n-                    (self.width - surface.width) / 2, (self.bar.height - surface.height) / 2\n+                    (self.width - surface.width) / 2,\n+                    (self.bar.height - surface.height) / 2,\n                 )\n                 self.drawer.ctx.set_source(surface.pattern)\n                 self.drawer.ctx.paint()\n@@ -173,12 +175,16 @@\n \n     def _get_layout_names(self):\n         \"\"\"\n-        Returns a list of tuples of lowercased layout name and class name strings for each available layout.\n+        Returns a sequence of tuples of layout name and lowercased class name\n+        strings for each available layout.\n         \"\"\"\n-        return [\n-            (layout.name, layout.__class__.__name__.lower())\n-            for layout in self.qtile.config.layouts\n-        ]\n+\n+        layouts = itertools.chain(\n+            self.qtile.config.layouts,\n+            (layout for group in self.qtile.config.groups for layout in group.layouts),\n+        )\n+\n+        return set((layout.name, layout.__class__.__name__.lower()) for layout in layouts)\n \n     def _update_icon_paths(self):\n         self.icon_paths = []\n", "issue": "CurrentLayoutIcon widget does not load icons for group-specific layouts\n### Issue description\n\nWhen using the `CurrentLayoutIcon` widget with layouts that are defined group-only,\r\n(For example: \r\n```py\r\nlayouts = [\r\n    # Some layouts here\r\n]\r\ngroups = [\r\n    Group(\"0\", layouts=[\r\n        # Any layouts not included in `layouts` above\r\n    ])\r\n]\r\n```\r\n)\r\nthe icon does not properly load and display for layouts that are only defined for the group specifically.\r\n\r\nThis issue occurs because in the setup function for loading icons https://github.com/qtile/qtile/blob/fd93e74c110f23846af5f721195b6c1f90b873f8/libqtile/widget/currentlayout.py#L207-L235\r\n it only loads icons for layouts found by `_get_layout_names`, which only includes layouts defined under `layouts` in the main configuration https://github.com/qtile/qtile/blob/fd93e74c110f23846af5f721195b6c1f90b873f8/libqtile/widget/currentlayout.py#L174-L181\r\n\r\nThis means that layouts are accessible that do not have icons that are loaded, leading to errors in the form\r\n```\r\n2023-09-24 23:31:50,483 ERROR libqtile currentlayout.py:draw():L158 No icon for layout <layout name>\r\n```\r\nIndicating that the issue isn't that an icon file wasn't found (I have them present in `~/.icons`, and no error message saying they aren't found), but rather that an icon file was never loaded.\r\n\r\nA potential solution would be to also go through each group in `_get_layout_names` to also include those layouts, deduplicating as necessary.\n\n### Version\n\n0.22.1\n\n### Backend\n\nX11 (default)\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": "# -*- coding: utf-8 -*-\n# Copyright (c) 2011 Florian Mounier\n# Copyright (c) 2011 Kenji_Takahashi\n# Copyright (c) 2012 roger\n# Copyright (c) 2012, 2014 Tycho Andersen\n# Copyright (c) 2012 Maximilian K\u00f6hl\n# Copyright (c) 2013 Craig Barnes\n# Copyright (c) 2014 Sean Vig\n# Copyright (c) 2014 Adi Sieker\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 os\n\nfrom libqtile import bar, hook\nfrom libqtile.images import Img\nfrom libqtile.log_utils import logger\nfrom libqtile.widget import base\n\n\nclass CurrentLayout(base._TextBox):\n    \"\"\"\n    Display the name of the current layout of the current group of the screen,\n    the bar containing the widget, is on.\n    \"\"\"\n\n    def __init__(self, width=bar.CALCULATED, **config):\n        base._TextBox.__init__(self, \"\", width, **config)\n\n    def _configure(self, qtile, bar):\n        base._TextBox._configure(self, qtile, bar)\n        layout_id = self.bar.screen.group.current_layout\n        self.text = self.bar.screen.group.layouts[layout_id].name\n        self.setup_hooks()\n\n        self.add_callbacks(\n            {\n                \"Button1\": qtile.next_layout,\n                \"Button2\": qtile.prev_layout,\n            }\n        )\n\n    def hook_response(self, layout, group):\n        if group.screen is not None and group.screen == self.bar.screen:\n            self.text = layout.name\n            self.bar.draw()\n\n    def setup_hooks(self):\n        hook.subscribe.layout_change(self.hook_response)\n\n    def remove_hooks(self):\n        hook.unsubscribe.layout_change(self.hook_response)\n\n    def finalize(self):\n        self.remove_hooks()\n        base._TextBox.finalize(self)\n\n\nclass CurrentLayoutIcon(base._TextBox):\n    \"\"\"\n    Display the icon representing the current layout of the\n    current group of the screen on which the bar containing the widget is.\n\n    If you are using custom layouts, a default icon with question mark\n    will be displayed for them. If you want to use custom icon for your own\n    layout, for example, `FooGrid`, then create a file named\n    \"layout-foogrid.png\" and place it in `~/.icons` directory. You can as well\n    use other directories, but then you need to specify those directories\n    in `custom_icon_paths` argument for this plugin.\n\n    The widget will look for icons with a `png` or `svg` extension.\n\n    The order of icon search is:\n\n    - dirs in `custom_icon_paths` config argument\n    - `~/.icons`\n    - built-in qtile icons\n    \"\"\"\n\n    orientations = base.ORIENTATION_HORIZONTAL\n\n    defaults = [\n        (\"scale\", 1, \"Scale factor relative to the bar height. Defaults to 1\"),\n        (\n            \"custom_icon_paths\",\n            [],\n            \"List of folders where to search icons before\"\n            \"using built-in icons or icons in ~/.icons dir.  \"\n            \"This can also be used to provide\"\n            \"missing icons for custom layouts.  \"\n            \"Defaults to empty list.\",\n        ),\n    ]\n\n    def __init__(self, **config):\n        base._TextBox.__init__(self, \"\", **config)\n        self.add_defaults(CurrentLayoutIcon.defaults)\n\n        self.length_type = bar.STATIC\n        self.length = 0\n\n    def _configure(self, qtile, bar):\n        base._TextBox._configure(self, qtile, bar)\n        layout_id = self.bar.screen.group.current_layout\n        self.text = self.bar.screen.group.layouts[layout_id].name\n        self.current_layout = self.text\n        self.icons_loaded = False\n        self.icon_paths = []\n        self.surfaces = {}\n        self._update_icon_paths()\n        self._setup_images()\n        self._setup_hooks()\n\n        self.add_callbacks(\n            {\n                \"Button1\": qtile.next_layout,\n                \"Button2\": qtile.prev_layout,\n            }\n        )\n\n    def hook_response(self, layout, group):\n        if group.screen is not None and group.screen == self.bar.screen:\n            self.current_layout = layout.name\n            self.bar.draw()\n\n    def _setup_hooks(self):\n        \"\"\"\n        Listens for layout change and performs a redraw when it occurs.\n        \"\"\"\n        hook.subscribe.layout_change(self.hook_response)\n\n    def _remove_hooks(self):\n        \"\"\"\n        Listens for layout change and performs a redraw when it occurs.\n        \"\"\"\n        hook.unsubscribe.layout_change(self.hook_response)\n\n    def draw(self):\n        if self.icons_loaded:\n            try:\n                surface = self.surfaces[self.current_layout]\n            except KeyError:\n                logger.error(\"No icon for layout %s\", self.current_layout)\n            else:\n                self.drawer.clear(self.background or self.bar.background)\n                self.drawer.ctx.save()\n                self.drawer.ctx.translate(\n                    (self.width - surface.width) / 2, (self.bar.height - surface.height) / 2\n                )\n                self.drawer.ctx.set_source(surface.pattern)\n                self.drawer.ctx.paint()\n                self.drawer.ctx.restore()\n                self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)\n        else:\n            # Fallback to text\n            self.text = self.current_layout[0].upper()\n            base._TextBox.draw(self)\n\n    def _get_layout_names(self):\n        \"\"\"\n        Returns a list of tuples of lowercased layout name and class name strings for each available layout.\n        \"\"\"\n        return [\n            (layout.name, layout.__class__.__name__.lower())\n            for layout in self.qtile.config.layouts\n        ]\n\n    def _update_icon_paths(self):\n        self.icon_paths = []\n\n        # We allow user to override icon search path\n        self.icon_paths.extend(os.path.expanduser(path) for path in self.custom_icon_paths)\n\n        # We also look in ~/.icons/ and ~/.local/share/icons\n        self.icon_paths.append(os.path.expanduser(\"~/.icons\"))\n        self.icon_paths.append(os.path.expanduser(\"~/.local/share/icons\"))\n\n        # Default icons are in libqtile/resources/layout-icons.\n        # If using default config without any custom icons,\n        # this path will be used.\n        root = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-2])\n        self.icon_paths.append(os.path.join(root, \"resources\", \"layout-icons\"))\n\n    def find_icon_file_path(self, layout_name):\n        for icon_path in self.icon_paths:\n            for extension in [\"png\", \"svg\"]:\n                icon_filename = \"layout-{}.{}\".format(layout_name, extension)\n                icon_file_path = os.path.join(icon_path, icon_filename)\n                if os.path.isfile(icon_file_path):\n                    return icon_file_path\n\n    def _setup_images(self):\n        \"\"\"\n        Loads layout icons.\n        \"\"\"\n        for names in self._get_layout_names():\n            layout_name = names[0]\n            # Python doesn't have an ordered set but we can use a dictionary instead\n            # First key is the layout's name (which may have been set by the user),\n            # the second is the class name. If these are the same (i.e. the user hasn't\n            # set a name) then there is only one key in the dictionary.\n            layouts = dict.fromkeys(names)\n            for layout in layouts.keys():\n                icon_file_path = self.find_icon_file_path(layout)\n                if icon_file_path:\n                    break\n            else:\n                logger.warning('No icon found for layout \"%s\"', layout_name)\n                icon_file_path = self.find_icon_file_path(\"unknown\")\n\n            img = Img.from_path(icon_file_path)\n\n            new_height = (self.bar.height - 2) * self.scale\n            img.resize(height=new_height)\n            if img.width > self.length:\n                self.length = img.width + self.actual_padding * 2\n\n            self.surfaces[layout_name] = img\n\n        self.icons_loaded = True\n\n    def finalize(self):\n        self._remove_hooks()\n        base._TextBox.finalize(self)\n", "path": "libqtile/widget/currentlayout.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) 2011 Florian Mounier\n# Copyright (c) 2011 Kenji_Takahashi\n# Copyright (c) 2012 roger\n# Copyright (c) 2012, 2014 Tycho Andersen\n# Copyright (c) 2012 Maximilian K\u00f6hl\n# Copyright (c) 2013 Craig Barnes\n# Copyright (c) 2014 Sean Vig\n# Copyright (c) 2014 Adi Sieker\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 itertools\nimport os\n\nfrom libqtile import bar, hook\nfrom libqtile.images import Img\nfrom libqtile.log_utils import logger\nfrom libqtile.widget import base\n\n\nclass CurrentLayout(base._TextBox):\n    \"\"\"\n    Display the name of the current layout of the current group of the screen,\n    the bar containing the widget, is on.\n    \"\"\"\n\n    def __init__(self, width=bar.CALCULATED, **config):\n        base._TextBox.__init__(self, \"\", width, **config)\n\n    def _configure(self, qtile, bar):\n        base._TextBox._configure(self, qtile, bar)\n        layout_id = self.bar.screen.group.current_layout\n        self.text = self.bar.screen.group.layouts[layout_id].name\n        self.setup_hooks()\n\n        self.add_callbacks(\n            {\n                \"Button1\": qtile.next_layout,\n                \"Button2\": qtile.prev_layout,\n            }\n        )\n\n    def hook_response(self, layout, group):\n        if group.screen is not None and group.screen == self.bar.screen:\n            self.text = layout.name\n            self.bar.draw()\n\n    def setup_hooks(self):\n        hook.subscribe.layout_change(self.hook_response)\n\n    def remove_hooks(self):\n        hook.unsubscribe.layout_change(self.hook_response)\n\n    def finalize(self):\n        self.remove_hooks()\n        base._TextBox.finalize(self)\n\n\nclass CurrentLayoutIcon(base._TextBox):\n    \"\"\"\n    Display the icon representing the current layout of the\n    current group of the screen on which the bar containing the widget is.\n\n    If you are using custom layouts, a default icon with question mark\n    will be displayed for them. If you want to use custom icon for your own\n    layout, for example, `FooGrid`, then create a file named\n    \"layout-foogrid.png\" and place it in `~/.icons` directory. You can as well\n    use other directories, but then you need to specify those directories\n    in `custom_icon_paths` argument for this plugin.\n\n    The widget will look for icons with a `png` or `svg` extension.\n\n    The order of icon search is:\n\n    - dirs in `custom_icon_paths` config argument\n    - `~/.icons`\n    - built-in qtile icons\n    \"\"\"\n\n    orientations = base.ORIENTATION_HORIZONTAL\n\n    defaults = [\n        (\"scale\", 1, \"Scale factor relative to the bar height. Defaults to 1\"),\n        (\n            \"custom_icon_paths\",\n            [],\n            \"List of folders where to search icons before\"\n            \"using built-in icons or icons in ~/.icons dir.  \"\n            \"This can also be used to provide\"\n            \"missing icons for custom layouts.  \"\n            \"Defaults to empty list.\",\n        ),\n    ]\n\n    def __init__(self, **config):\n        base._TextBox.__init__(self, \"\", **config)\n        self.add_defaults(CurrentLayoutIcon.defaults)\n\n        self.length_type = bar.STATIC\n        self.length = 0\n\n    def _configure(self, qtile, bar):\n        base._TextBox._configure(self, qtile, bar)\n        layout_id = self.bar.screen.group.current_layout\n        self.text = self.bar.screen.group.layouts[layout_id].name\n        self.current_layout = self.text\n        self.icons_loaded = False\n        self.icon_paths = []\n        self.surfaces = {}\n        self._update_icon_paths()\n        self._setup_images()\n        self._setup_hooks()\n\n        self.add_callbacks(\n            {\n                \"Button1\": qtile.next_layout,\n                \"Button2\": qtile.prev_layout,\n            }\n        )\n\n    def hook_response(self, layout, group):\n        if group.screen is not None and group.screen == self.bar.screen:\n            self.current_layout = layout.name\n            self.bar.draw()\n\n    def _setup_hooks(self):\n        \"\"\"\n        Listens for layout change and performs a redraw when it occurs.\n        \"\"\"\n        hook.subscribe.layout_change(self.hook_response)\n\n    def _remove_hooks(self):\n        \"\"\"\n        Listens for layout change and performs a redraw when it occurs.\n        \"\"\"\n        hook.unsubscribe.layout_change(self.hook_response)\n\n    def draw(self):\n        if self.icons_loaded:\n            try:\n                surface = self.surfaces[self.current_layout]\n            except KeyError:\n                logger.error(\"No icon for layout %s\", self.current_layout)\n            else:\n                self.drawer.clear(self.background or self.bar.background)\n                self.drawer.ctx.save()\n                self.drawer.ctx.translate(\n                    (self.width - surface.width) / 2,\n                    (self.bar.height - surface.height) / 2,\n                )\n                self.drawer.ctx.set_source(surface.pattern)\n                self.drawer.ctx.paint()\n                self.drawer.ctx.restore()\n                self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.length)\n        else:\n            # Fallback to text\n            self.text = self.current_layout[0].upper()\n            base._TextBox.draw(self)\n\n    def _get_layout_names(self):\n        \"\"\"\n        Returns a sequence of tuples of layout name and lowercased class name\n        strings for each available layout.\n        \"\"\"\n\n        layouts = itertools.chain(\n            self.qtile.config.layouts,\n            (layout for group in self.qtile.config.groups for layout in group.layouts),\n        )\n\n        return set((layout.name, layout.__class__.__name__.lower()) for layout in layouts)\n\n    def _update_icon_paths(self):\n        self.icon_paths = []\n\n        # We allow user to override icon search path\n        self.icon_paths.extend(os.path.expanduser(path) for path in self.custom_icon_paths)\n\n        # We also look in ~/.icons/ and ~/.local/share/icons\n        self.icon_paths.append(os.path.expanduser(\"~/.icons\"))\n        self.icon_paths.append(os.path.expanduser(\"~/.local/share/icons\"))\n\n        # Default icons are in libqtile/resources/layout-icons.\n        # If using default config without any custom icons,\n        # this path will be used.\n        root = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-2])\n        self.icon_paths.append(os.path.join(root, \"resources\", \"layout-icons\"))\n\n    def find_icon_file_path(self, layout_name):\n        for icon_path in self.icon_paths:\n            for extension in [\"png\", \"svg\"]:\n                icon_filename = \"layout-{}.{}\".format(layout_name, extension)\n                icon_file_path = os.path.join(icon_path, icon_filename)\n                if os.path.isfile(icon_file_path):\n                    return icon_file_path\n\n    def _setup_images(self):\n        \"\"\"\n        Loads layout icons.\n        \"\"\"\n        for names in self._get_layout_names():\n            layout_name = names[0]\n            # Python doesn't have an ordered set but we can use a dictionary instead\n            # First key is the layout's name (which may have been set by the user),\n            # the second is the class name. If these are the same (i.e. the user hasn't\n            # set a name) then there is only one key in the dictionary.\n            layouts = dict.fromkeys(names)\n            for layout in layouts.keys():\n                icon_file_path = self.find_icon_file_path(layout)\n                if icon_file_path:\n                    break\n            else:\n                logger.warning('No icon found for layout \"%s\"', layout_name)\n                icon_file_path = self.find_icon_file_path(\"unknown\")\n\n            img = Img.from_path(icon_file_path)\n\n            new_height = (self.bar.height - 2) * self.scale\n            img.resize(height=new_height)\n            if img.width > self.length:\n                self.length = img.width + self.actual_padding * 2\n\n            self.surfaces[layout_name] = img\n\n        self.icons_loaded = True\n\n    def finalize(self):\n        self._remove_hooks()\n        base._TextBox.finalize(self)\n", "path": "libqtile/widget/currentlayout.py"}]}