We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
simple_key_binder doesn't work after using a KeyChord
### The issue:

Qtile 0.22.1, in logs no errors or warns.
simple_key_binder doesn't work after using a KeyChord.
If I use simple_key_binder to generate Group switching keys, then Group switching does not work after using KeyChord. I am forced to do reload_config.
For the solution, I had to use: for i in Groups.
I see: https://github.com/qtile/qtile/issues/3734
My full config (correct) and line: https://github.com/Koljasha/archlinux/blob/7bef1b8d4d4fc972cdcbfa90453b5967ff739227/config/qtile/config.py#L279
For error: uncomment L279, comment L281-L290

### Required:

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

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `libqtile/dgroups.py`
Content:
```
1 # Copyright (c) 2011-2012 Florian Mounier
2 # Copyright (c) 2012-2014 roger
3 # Copyright (c) 2012 Craig Barnes
4 # Copyright (c) 2012-2014 Tycho Andersen
5 # Copyright (c) 2013 Tao Sauvage
6 # Copyright (c) 2014 ramnes
7 # Copyright (c) 2014 Sebastian Kricner
8 # Copyright (c) 2014 Sean Vig
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining a copy
11 # of this software and associated documentation files (the "Software"), to deal
12 # in the Software without restriction, including without limitation the rights
13 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 # copies of the Software, and to permit persons to whom the Software is
15 # furnished to do so, subject to the following conditions:
16 #
17 # The above copyright notice and this permission notice shall be included in
18 # all copies or substantial portions of the Software.
19 #
20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 # SOFTWARE.
27 
28 import collections
29 
30 import libqtile.hook
31 from libqtile.backend.base import Static
32 from libqtile.command import lazy
33 from libqtile.config import Group, Key, Match, Rule
34 from libqtile.log_utils import logger
35 
36 
37 def simple_key_binder(mod, keynames=None):
38     """Bind keys to mod+group position or to the keys specified as second argument"""
39 
40     def func(dgroup):
41         # unbind all
42         for key in dgroup.keys[:]:
43             dgroup.qtile.ungrab_key(key)
44             dgroup.keys.remove(key)
45 
46         if keynames:
47             keys = keynames
48         else:
49             # keys 1 to 9 and 0
50             keys = list(map(str, list(range(1, 10)) + [0]))
51 
52         # bind all keys
53         for keyname, group in zip(keys, dgroup.qtile.groups):
54             name = group.name
55             key = Key([mod], keyname, lazy.group[name].toscreen())
56             key_s = Key([mod, "shift"], keyname, lazy.window.togroup(name))
57             key_c = Key([mod, "control"], keyname, lazy.group.switch_groups(name))
58             dgroup.keys.append(key)
59             dgroup.keys.append(key_s)
60             dgroup.keys.append(key_c)
61             dgroup.qtile.grab_key(key)
62             dgroup.qtile.grab_key(key_s)
63             dgroup.qtile.grab_key(key_c)
64 
65     return func
66 
67 
68 class DGroups:
69     """Dynamic Groups"""
70 
71     def __init__(self, qtile, dgroups, key_binder=None, delay=1):
72         self.qtile = qtile
73 
74         self.groups = dgroups
75         self.groups_map = {}
76 
77         self.rules = []
78         self.rules_map = {}
79         self.last_rule_id = 0
80 
81         for rule in getattr(qtile.config, "dgroups_app_rules", []):
82             self.add_rule(rule)
83 
84         self.keys = []
85 
86         self.key_binder = key_binder
87 
88         self._setup_hooks()
89         self._setup_groups()
90 
91         self.delay = delay
92 
93         self.timeout = {}
94 
95     def add_rule(self, rule, last=True):
96         rule_id = self.last_rule_id
97         self.rules_map[rule_id] = rule
98         if last:
99             self.rules.append(rule)
100         else:
101             self.rules.insert(0, rule)
102         self.last_rule_id += 1
103         return rule_id
104 
105     def remove_rule(self, rule_id):
106         rule = self.rules_map.get(rule_id)
107         if rule:
108             self.rules.remove(rule)
109             del self.rules_map[rule_id]
110         else:
111             logger.warning('Rule "%s" not found', rule_id)
112 
113     def add_dgroup(self, group, start=False):
114         self.groups_map[group.name] = group
115         rule = Rule(group.matches, group=group.name)
116         self.rules.append(rule)
117         if start:
118             self.qtile.add_group(group.name, group.layout, group.layouts, group.label)
119 
120     def _setup_groups(self):
121         for group in self.groups:
122             self.add_dgroup(group, group.init)
123 
124             if group.spawn and not self.qtile.no_spawn:
125                 if isinstance(group.spawn, str):
126                     spawns = [group.spawn]
127                 else:
128                     spawns = group.spawn
129                 for spawn in spawns:
130                     pid = self.qtile.spawn(spawn)
131                     self.add_rule(Rule(Match(net_wm_pid=pid), group.name))
132 
133     def _setup_hooks(self):
134         libqtile.hook.subscribe.addgroup(self._addgroup)
135         libqtile.hook.subscribe.client_new(self._add)
136         libqtile.hook.subscribe.client_killed(self._del)
137         if self.key_binder:
138             libqtile.hook.subscribe.setgroup(lambda: self.key_binder(self))
139             libqtile.hook.subscribe.changegroup(lambda: self.key_binder(self))
140 
141     def _addgroup(self, group_name):
142         if group_name not in self.groups_map:
143             self.add_dgroup(Group(group_name, persist=False))
144 
145     def _add(self, client):
146         if client in self.timeout:
147             logger.debug("Remove dgroup source")
148             self.timeout.pop(client).cancel()
149 
150         # ignore static windows
151         if isinstance(client, Static):
152             return
153 
154         # ignore windows whose groups is already set (e.g. from another hook or
155         # when it was set on state restore)
156         if client.group is not None:
157             return
158 
159         group_set = False
160         intrusive = False
161 
162         for rule in self.rules:
163             # Matching Rules
164             if rule.matches(client):
165                 if rule.group:
166                     if rule.group in self.groups_map:
167                         layout = self.groups_map[rule.group].layout
168                         layouts = self.groups_map[rule.group].layouts
169                         label = self.groups_map[rule.group].label
170                     else:
171                         layout = None
172                         layouts = None
173                         label = None
174                     group_added = self.qtile.add_group(rule.group, layout, layouts, label)
175                     client.togroup(rule.group)
176 
177                     group_set = True
178 
179                     group_obj = self.qtile.groups_map[rule.group]
180                     group = self.groups_map.get(rule.group)
181                     if group and group_added:
182                         for k, v in list(group.layout_opts.items()):
183                             if isinstance(v, collections.abc.Callable):
184                                 v(group_obj.layout)
185                             else:
186                                 setattr(group_obj.layout, k, v)
187                         affinity = group.screen_affinity
188                         if affinity and len(self.qtile.screens) > affinity:
189                             self.qtile.screens[affinity].set_group(group_obj)
190 
191                 if rule.float:
192                     client.enable_floating()
193 
194                 if rule.intrusive:
195                     intrusive = rule.intrusive
196 
197                 if rule.break_on_match:
198                     break
199 
200         # If app doesn't have a group
201         if not group_set:
202             current_group = self.qtile.current_group.name
203             if (
204                 current_group in self.groups_map
205                 and self.groups_map[current_group].exclusive
206                 and not intrusive
207             ):
208 
209                 wm_class = client.get_wm_class()
210 
211                 if wm_class:
212                     if len(wm_class) > 1:
213                         wm_class = wm_class[1]
214                     else:
215                         wm_class = wm_class[0]
216 
217                     group_name = wm_class
218                 else:
219                     group_name = client.name or "Unnamed"
220 
221                 self.add_dgroup(Group(group_name, persist=False), start=True)
222                 client.togroup(group_name)
223         self.sort_groups()
224 
225     def sort_groups(self):
226         grps = self.qtile.groups
227         sorted_grps = sorted(grps, key=lambda g: self.groups_map[g.name].position)
228         if grps != sorted_grps:
229             self.qtile.groups = sorted_grps
230             libqtile.hook.fire("changegroup")
231 
232     def _del(self, client):
233         # ignore static windows
234         if isinstance(client, Static):
235             return
236 
237         group = client.group
238 
239         def delete_client():
240             # Delete group if empty and don't persist
241             if (
242                 group
243                 and group.name in self.groups_map
244                 and not self.groups_map[group.name].persist
245                 and len(group.windows) <= 0
246             ):
247                 self.qtile.delete_group(group.name)
248                 self.sort_groups()
249             del self.timeout[client]
250 
251         logger.debug("Deleting %s in %ss", group, self.delay)
252         self.timeout[client] = self.qtile.call_later(self.delay, delete_client)
253
```
--- 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/dgroups.py b/libqtile/dgroups.py
--- a/libqtile/dgroups.py
+++ b/libqtile/dgroups.py
@@ -41,6 +41,7 @@
         # unbind all
         for key in dgroup.keys[:]:
             dgroup.qtile.ungrab_key(key)
+            dgroup.qtile.config.keys.remove(key)
             dgroup.keys.remove(key)
 
         if keynames:
@@ -55,9 +56,8 @@
             key = Key([mod], keyname, lazy.group[name].toscreen())
             key_s = Key([mod, "shift"], keyname, lazy.window.togroup(name))
             key_c = Key([mod, "control"], keyname, lazy.group.switch_groups(name))
-            dgroup.keys.append(key)
-            dgroup.keys.append(key_s)
-            dgroup.keys.append(key_c)
+            dgroup.keys.extend([key, key_s, key_c])
+            dgroup.qtile.config.keys.extend([key, key_s, key_c])
             dgroup.qtile.grab_key(key)
             dgroup.qtile.grab_key(key_s)
             dgroup.qtile.grab_key(key_c)

{"golden_diff": "diff --git a/libqtile/dgroups.py b/libqtile/dgroups.py\n--- a/libqtile/dgroups.py\n+++ b/libqtile/dgroups.py\n@@ -41,6 +41,7 @@\n         # unbind all\n         for key in dgroup.keys[:]:\n             dgroup.qtile.ungrab_key(key)\n+            dgroup.qtile.config.keys.remove(key)\n             dgroup.keys.remove(key)\n \n         if keynames:\n@@ -55,9 +56,8 @@\n             key = Key([mod], keyname, lazy.group[name].toscreen())\n             key_s = Key([mod, \"shift\"], keyname, lazy.window.togroup(name))\n             key_c = Key([mod, \"control\"], keyname, lazy.group.switch_groups(name))\n-            dgroup.keys.append(key)\n-            dgroup.keys.append(key_s)\n-            dgroup.keys.append(key_c)\n+            dgroup.keys.extend([key, key_s, key_c])\n+            dgroup.qtile.config.keys.extend([key, key_s, key_c])\n             dgroup.qtile.grab_key(key)\n             dgroup.qtile.grab_key(key_s)\n             dgroup.qtile.grab_key(key_c)\n", "issue": "simple_key_binder doesn't work after using a KeyChord\n### The issue:\n\nQtile 0.22.1, in logs no errors or warns.\r\nsimple_key_binder doesn't work after using a KeyChord.\r\nIf I use simple_key_binder to generate Group switching keys, then Group switching does not work after using KeyChord. I am forced to do reload_config.\r\nFor the solution, I had to use: for i in Groups.\r\nI see: https://github.com/qtile/qtile/issues/3734\r\nMy full config (correct) and line: https://github.com/Koljasha/archlinux/blob/7bef1b8d4d4fc972cdcbfa90453b5967ff739227/config/qtile/config.py#L279\r\nFor error: uncomment L279, comment L281-L290\n\n### Required:\n\n- [X] I have searched past issues to see if this bug has already been reported.\n", "before_files": [{"content": "# Copyright (c) 2011-2012 Florian Mounier\n# Copyright (c) 2012-2014 roger\n# Copyright (c) 2012 Craig Barnes\n# Copyright (c) 2012-2014 Tycho Andersen\n# Copyright (c) 2013 Tao Sauvage\n# Copyright (c) 2014 ramnes\n# Copyright (c) 2014 Sebastian Kricner\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.\n\nimport collections\n\nimport libqtile.hook\nfrom libqtile.backend.base import Static\nfrom libqtile.command import lazy\nfrom libqtile.config import Group, Key, Match, Rule\nfrom libqtile.log_utils import logger\n\n\ndef simple_key_binder(mod, keynames=None):\n    \"\"\"Bind keys to mod+group position or to the keys specified as second argument\"\"\"\n\n    def func(dgroup):\n        # unbind all\n        for key in dgroup.keys[:]:\n            dgroup.qtile.ungrab_key(key)\n            dgroup.keys.remove(key)\n\n        if keynames:\n            keys = keynames\n        else:\n            # keys 1 to 9 and 0\n            keys = list(map(str, list(range(1, 10)) + [0]))\n\n        # bind all keys\n        for keyname, group in zip(keys, dgroup.qtile.groups):\n            name = group.name\n            key = Key([mod], keyname, lazy.group[name].toscreen())\n            key_s = Key([mod, \"shift\"], keyname, lazy.window.togroup(name))\n            key_c = Key([mod, \"control\"], keyname, lazy.group.switch_groups(name))\n            dgroup.keys.append(key)\n            dgroup.keys.append(key_s)\n            dgroup.keys.append(key_c)\n            dgroup.qtile.grab_key(key)\n            dgroup.qtile.grab_key(key_s)\n            dgroup.qtile.grab_key(key_c)\n\n    return func\n\n\nclass DGroups:\n    \"\"\"Dynamic Groups\"\"\"\n\n    def __init__(self, qtile, dgroups, key_binder=None, delay=1):\n        self.qtile = qtile\n\n        self.groups = dgroups\n        self.groups_map = {}\n\n        self.rules = []\n        self.rules_map = {}\n        self.last_rule_id = 0\n\n        for rule in getattr(qtile.config, \"dgroups_app_rules\", []):\n            self.add_rule(rule)\n\n        self.keys = []\n\n        self.key_binder = key_binder\n\n        self._setup_hooks()\n        self._setup_groups()\n\n        self.delay = delay\n\n        self.timeout = {}\n\n    def add_rule(self, rule, last=True):\n        rule_id = self.last_rule_id\n        self.rules_map[rule_id] = rule\n        if last:\n            self.rules.append(rule)\n        else:\n            self.rules.insert(0, rule)\n        self.last_rule_id += 1\n        return rule_id\n\n    def remove_rule(self, rule_id):\n        rule = self.rules_map.get(rule_id)\n        if rule:\n            self.rules.remove(rule)\n            del self.rules_map[rule_id]\n        else:\n            logger.warning('Rule \"%s\" not found', rule_id)\n\n    def add_dgroup(self, group, start=False):\n        self.groups_map[group.name] = group\n        rule = Rule(group.matches, group=group.name)\n        self.rules.append(rule)\n        if start:\n            self.qtile.add_group(group.name, group.layout, group.layouts, group.label)\n\n    def _setup_groups(self):\n        for group in self.groups:\n            self.add_dgroup(group, group.init)\n\n            if group.spawn and not self.qtile.no_spawn:\n                if isinstance(group.spawn, str):\n                    spawns = [group.spawn]\n                else:\n                    spawns = group.spawn\n                for spawn in spawns:\n                    pid = self.qtile.spawn(spawn)\n                    self.add_rule(Rule(Match(net_wm_pid=pid), group.name))\n\n    def _setup_hooks(self):\n        libqtile.hook.subscribe.addgroup(self._addgroup)\n        libqtile.hook.subscribe.client_new(self._add)\n        libqtile.hook.subscribe.client_killed(self._del)\n        if self.key_binder:\n            libqtile.hook.subscribe.setgroup(lambda: self.key_binder(self))\n            libqtile.hook.subscribe.changegroup(lambda: self.key_binder(self))\n\n    def _addgroup(self, group_name):\n        if group_name not in self.groups_map:\n            self.add_dgroup(Group(group_name, persist=False))\n\n    def _add(self, client):\n        if client in self.timeout:\n            logger.debug(\"Remove dgroup source\")\n            self.timeout.pop(client).cancel()\n\n        # ignore static windows\n        if isinstance(client, Static):\n            return\n\n        # ignore windows whose groups is already set (e.g. from another hook or\n        # when it was set on state restore)\n        if client.group is not None:\n            return\n\n        group_set = False\n        intrusive = False\n\n        for rule in self.rules:\n            # Matching Rules\n            if rule.matches(client):\n                if rule.group:\n                    if rule.group in self.groups_map:\n                        layout = self.groups_map[rule.group].layout\n                        layouts = self.groups_map[rule.group].layouts\n                        label = self.groups_map[rule.group].label\n                    else:\n                        layout = None\n                        layouts = None\n                        label = None\n                    group_added = self.qtile.add_group(rule.group, layout, layouts, label)\n                    client.togroup(rule.group)\n\n                    group_set = True\n\n                    group_obj = self.qtile.groups_map[rule.group]\n                    group = self.groups_map.get(rule.group)\n                    if group and group_added:\n                        for k, v in list(group.layout_opts.items()):\n                            if isinstance(v, collections.abc.Callable):\n                                v(group_obj.layout)\n                            else:\n                                setattr(group_obj.layout, k, v)\n                        affinity = group.screen_affinity\n                        if affinity and len(self.qtile.screens) > affinity:\n                            self.qtile.screens[affinity].set_group(group_obj)\n\n                if rule.float:\n                    client.enable_floating()\n\n                if rule.intrusive:\n                    intrusive = rule.intrusive\n\n                if rule.break_on_match:\n                    break\n\n        # If app doesn't have a group\n        if not group_set:\n            current_group = self.qtile.current_group.name\n            if (\n                current_group in self.groups_map\n                and self.groups_map[current_group].exclusive\n                and not intrusive\n            ):\n\n                wm_class = client.get_wm_class()\n\n                if wm_class:\n                    if len(wm_class) > 1:\n                        wm_class = wm_class[1]\n                    else:\n                        wm_class = wm_class[0]\n\n                    group_name = wm_class\n                else:\n                    group_name = client.name or \"Unnamed\"\n\n                self.add_dgroup(Group(group_name, persist=False), start=True)\n                client.togroup(group_name)\n        self.sort_groups()\n\n    def sort_groups(self):\n        grps = self.qtile.groups\n        sorted_grps = sorted(grps, key=lambda g: self.groups_map[g.name].position)\n        if grps != sorted_grps:\n            self.qtile.groups = sorted_grps\n            libqtile.hook.fire(\"changegroup\")\n\n    def _del(self, client):\n        # ignore static windows\n        if isinstance(client, Static):\n            return\n\n        group = client.group\n\n        def delete_client():\n            # Delete group if empty and don't persist\n            if (\n                group\n                and group.name in self.groups_map\n                and not self.groups_map[group.name].persist\n                and len(group.windows) <= 0\n            ):\n                self.qtile.delete_group(group.name)\n                self.sort_groups()\n            del self.timeout[client]\n\n        logger.debug(\"Deleting %s in %ss\", group, self.delay)\n        self.timeout[client] = self.qtile.call_later(self.delay, delete_client)\n", "path": "libqtile/dgroups.py"}], "after_files": [{"content": "# Copyright (c) 2011-2012 Florian Mounier\n# Copyright (c) 2012-2014 roger\n# Copyright (c) 2012 Craig Barnes\n# Copyright (c) 2012-2014 Tycho Andersen\n# Copyright (c) 2013 Tao Sauvage\n# Copyright (c) 2014 ramnes\n# Copyright (c) 2014 Sebastian Kricner\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.\n\nimport collections\n\nimport libqtile.hook\nfrom libqtile.backend.base import Static\nfrom libqtile.command import lazy\nfrom libqtile.config import Group, Key, Match, Rule\nfrom libqtile.log_utils import logger\n\n\ndef simple_key_binder(mod, keynames=None):\n    \"\"\"Bind keys to mod+group position or to the keys specified as second argument\"\"\"\n\n    def func(dgroup):\n        # unbind all\n        for key in dgroup.keys[:]:\n            dgroup.qtile.ungrab_key(key)\n            dgroup.qtile.config.keys.remove(key)\n            dgroup.keys.remove(key)\n\n        if keynames:\n            keys = keynames\n        else:\n            # keys 1 to 9 and 0\n            keys = list(map(str, list(range(1, 10)) + [0]))\n\n        # bind all keys\n        for keyname, group in zip(keys, dgroup.qtile.groups):\n            name = group.name\n            key = Key([mod], keyname, lazy.group[name].toscreen())\n            key_s = Key([mod, \"shift\"], keyname, lazy.window.togroup(name))\n            key_c = Key([mod, \"control\"], keyname, lazy.group.switch_groups(name))\n            dgroup.keys.extend([key, key_s, key_c])\n            dgroup.qtile.config.keys.extend([key, key_s, key_c])\n            dgroup.qtile.grab_key(key)\n            dgroup.qtile.grab_key(key_s)\n            dgroup.qtile.grab_key(key_c)\n\n    return func\n\n\nclass DGroups:\n    \"\"\"Dynamic Groups\"\"\"\n\n    def __init__(self, qtile, dgroups, key_binder=None, delay=1):\n        self.qtile = qtile\n\n        self.groups = dgroups\n        self.groups_map = {}\n\n        self.rules = []\n        self.rules_map = {}\n        self.last_rule_id = 0\n\n        for rule in getattr(qtile.config, \"dgroups_app_rules\", []):\n            self.add_rule(rule)\n\n        self.keys = []\n\n        self.key_binder = key_binder\n\n        self._setup_hooks()\n        self._setup_groups()\n\n        self.delay = delay\n\n        self.timeout = {}\n\n    def add_rule(self, rule, last=True):\n        rule_id = self.last_rule_id\n        self.rules_map[rule_id] = rule\n        if last:\n            self.rules.append(rule)\n        else:\n            self.rules.insert(0, rule)\n        self.last_rule_id += 1\n        return rule_id\n\n    def remove_rule(self, rule_id):\n        rule = self.rules_map.get(rule_id)\n        if rule:\n            self.rules.remove(rule)\n            del self.rules_map[rule_id]\n        else:\n            logger.warning('Rule \"%s\" not found', rule_id)\n\n    def add_dgroup(self, group, start=False):\n        self.groups_map[group.name] = group\n        rule = Rule(group.matches, group=group.name)\n        self.rules.append(rule)\n        if start:\n            self.qtile.add_group(group.name, group.layout, group.layouts, group.label)\n\n    def _setup_groups(self):\n        for group in self.groups:\n            self.add_dgroup(group, group.init)\n\n            if group.spawn and not self.qtile.no_spawn:\n                if isinstance(group.spawn, str):\n                    spawns = [group.spawn]\n                else:\n                    spawns = group.spawn\n                for spawn in spawns:\n                    pid = self.qtile.spawn(spawn)\n                    self.add_rule(Rule(Match(net_wm_pid=pid), group.name))\n\n    def _setup_hooks(self):\n        libqtile.hook.subscribe.addgroup(self._addgroup)\n        libqtile.hook.subscribe.client_new(self._add)\n        libqtile.hook.subscribe.client_killed(self._del)\n        if self.key_binder:\n            libqtile.hook.subscribe.setgroup(lambda: self.key_binder(self))\n            libqtile.hook.subscribe.changegroup(lambda: self.key_binder(self))\n\n    def _addgroup(self, group_name):\n        if group_name not in self.groups_map:\n            self.add_dgroup(Group(group_name, persist=False))\n\n    def _add(self, client):\n        if client in self.timeout:\n            logger.debug(\"Remove dgroup source\")\n            self.timeout.pop(client).cancel()\n\n        # ignore static windows\n        if isinstance(client, Static):\n            return\n\n        # ignore windows whose groups is already set (e.g. from another hook or\n        # when it was set on state restore)\n        if client.group is not None:\n            return\n\n        group_set = False\n        intrusive = False\n\n        for rule in self.rules:\n            # Matching Rules\n            if rule.matches(client):\n                if rule.group:\n                    if rule.group in self.groups_map:\n                        layout = self.groups_map[rule.group].layout\n                        layouts = self.groups_map[rule.group].layouts\n                        label = self.groups_map[rule.group].label\n                    else:\n                        layout = None\n                        layouts = None\n                        label = None\n                    group_added = self.qtile.add_group(rule.group, layout, layouts, label)\n                    client.togroup(rule.group)\n\n                    group_set = True\n\n                    group_obj = self.qtile.groups_map[rule.group]\n                    group = self.groups_map.get(rule.group)\n                    if group and group_added:\n                        for k, v in list(group.layout_opts.items()):\n                            if isinstance(v, collections.abc.Callable):\n                                v(group_obj.layout)\n                            else:\n                                setattr(group_obj.layout, k, v)\n                        affinity = group.screen_affinity\n                        if affinity and len(self.qtile.screens) > affinity:\n                            self.qtile.screens[affinity].set_group(group_obj)\n\n                if rule.float:\n                    client.enable_floating()\n\n                if rule.intrusive:\n                    intrusive = rule.intrusive\n\n                if rule.break_on_match:\n                    break\n\n        # If app doesn't have a group\n        if not group_set:\n            current_group = self.qtile.current_group.name\n            if (\n                current_group in self.groups_map\n                and self.groups_map[current_group].exclusive\n                and not intrusive\n            ):\n\n                wm_class = client.get_wm_class()\n\n                if wm_class:\n                    if len(wm_class) > 1:\n                        wm_class = wm_class[1]\n                    else:\n                        wm_class = wm_class[0]\n\n                    group_name = wm_class\n                else:\n                    group_name = client.name or \"Unnamed\"\n\n                self.add_dgroup(Group(group_name, persist=False), start=True)\n                client.togroup(group_name)\n        self.sort_groups()\n\n    def sort_groups(self):\n        grps = self.qtile.groups\n        sorted_grps = sorted(grps, key=lambda g: self.groups_map[g.name].position)\n        if grps != sorted_grps:\n            self.qtile.groups = sorted_grps\n            libqtile.hook.fire(\"changegroup\")\n\n    def _del(self, client):\n        # ignore static windows\n        if isinstance(client, Static):\n            return\n\n        group = client.group\n\n        def delete_client():\n            # Delete group if empty and don't persist\n            if (\n                group\n                and group.name in self.groups_map\n                and not self.groups_map[group.name].persist\n                and len(group.windows) <= 0\n            ):\n                self.qtile.delete_group(group.name)\n                self.sort_groups()\n            del self.timeout[client]\n\n        logger.debug(\"Deleting %s in %ss\", group, self.delay)\n        self.timeout[client] = self.qtile.call_later(self.delay, delete_client)\n", "path": "libqtile/dgroups.py"}]}