Repository: qtile/qtile

Issue:
Configured keyboards limited to 3
### The issue:

In my config I've defined 4 keyboard layouts, however in the widget on the bar, it never reaches the fourth layout when left-clicking. When the third layout is removed (moving the fourth into the third position) it is suddenly accessible so it's not a problem with the layout itself. I don't notice any logs that would apply to this.

```
widget.KeyboardLayout(
    font = defaults['font'],
    fontsize = defaults['fontsize'],
    configured_keyboards = ['us', 'es', 'semimak-jq', 'mtgap'],
    display_map = { #makes everything lowercase
        'us': 'us',
        'es': 'es',
        'workman': 'wm',
        'semimak': 'sm',
        'mtgap': 'mt',
    }
),
```

From my config, I defined us, es, semimak-jq, and mtgap, but I can never rotate onto mtgap unless I remove semimak-jq. When I manually set the layout with setxkbmap, it is correctly displayed as 'mt' in the widget, I just can't rotate onto it via left-click on the widget.

Qtile Version: 0.22.1, X11

### Required:

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


Discussion:
if you set the layout to seminak-jq, what do you get from
`setxkbmap -verbose 10 -query`
?
Looking at code, if we dont get exactly 'semimak-jq' from this return after the regex parsing https://github.com/qtile/qtile/blob/master/libqtile/widget/keyboardlayout.py#L85 it next_keyboard defaults to the first on the list https://github.com/qtile/qtile/blob/master/libqtile/widget/keyboardlayout.py#L188

Another check could be reversing the order of mtgap and the seminak-jq. If my assumption is correct, this way it should actually work (because the non-matching one becomes the last)
Result of running the command while semimak-jq is selected:
```
Setting verbose level to 10
locale is C
Trying to load rules file ./rules/evdev...
Trying to load rules file /usr/share/X11/xkb/rules/evdev...
Success.
Applied rules from evdev:
rules:      evdev
model:      pc105
layout:     semimak-jq
options:    terminate:ctrl_alt_bksp
Trying to build keymap using the following components:
keycodes:   evdev+aliases(qwerty)
types:      complete
compat:     complete
symbols:    pc+semimak-jq+inet(evdev)+terminate(ctrl_alt_bksp)
geometry:   pc(pc105)
rules:      evdev
model:      pc105
layout:     semimak-jq
options:    terminate:ctrl_alt_bksp
```

I switched the order and it does seem to have circumvented the issue. Even so, I went ahead and made my own tool to swap layouts, since another thing that bugged me about the built-in widget is the lack of right-click support. It's been working flawlessly for the last few months, but I only added X support. If people want to see it, here you go:

```
class KeyboardSwitcher(base.InLoopPollText):

    defaults = [
        ("update_interval", 1, "Update time in seconds."),
        (
            "configured_keyboards",
            ["us"],
            "A list of predefined keyboard layouts "
            "represented as strings. For example: "
            "['us', 'us colemak', 'es', 'fr'].",
        ),
        (
            "display_map",
            {},
            "Custom display of layout. Key should be in format "
            "'layout variant'. For example: "
            "{'us': 'us', 'lt sgs': 'sgs', 'ru phonetic': 'ru'}",
        ),
        ("option", None, "string of setxkbmap option. Ex., 'compose:menu,grp_led:scroll'"),
    ]

    def __init__(self, configured_keyboards = ["us"], display_map = dict()):
        widget.TextBox.__init__(self)
        self.add_callbacks(
            {
                "Button1": self.left_click,
                "Button3": self.right_click,
            }
        )
        self.font = defaults['font']
        self.fontsize = defaults['fontsize']
        self.add_defaults(KeyboardSwitcher.defaults)
        self.configured_keyboards = configured_keyboards
        self.display_map = display_map
        self.current = configured_keyboards[0]
        self.index: int = 0
        
    def _configure(self, qtile, bar):
        super()._configure(qtile, bar)

    def poll(self):
        return self.current
    
    def set_keymap(self, keymap: str):
        subprocess.Popen(["setxkbmap", keymap])

    def left_click(self):
        self.index = (self.index + 1) % len(self.configured_keyboards)
        new_keymap = self.configured_keyboards[self.index]
        if new_keymap in self.display_map:
            self.current = self.display_map[new_keymap]
        else:
            self.current = new_keymap
        self.set_keymap(new_keymap)
        self.tick()

    def right_click(self):
        self.index = (self.index - 1) % len(self.configured_keyboards)
        new_keymap = self.configured_keyboards[self.index]
        if new_keymap in self.display_map:
            self.current = self.display_map[new_keymap]
        else:
            self.current = new_keymap
        self.set_keymap(new_keymap)
        self.tick()

```

And then add this to your widget bar, customizing your layouts of course:
```
                KeyboardSwitcher(
                    configured_keyboards = ['us', 'es', 'semimak-jq', 'mtgap', 'colemak-dh'],
                    display_map = { #makes everything lowercase
                        'us': 'us',
                        'es': 'es',
                        'workman': 'wm',
                        'semimak-jq': 'sm',
                        'mtgap': 'mt',
                        'colemak-dh': 'cm',
                    }
                ),

```

As I thought, the issue stems from the regex. \w does not match the "-" in that layout (\w is equivalent to [a-zA-Z0-9_], but for layout formats [\w-]+ would be needed.
Tested poc
`
>>> import re
>>> s = """
... Setting verbose level to 10
... locale is C
... Trying to load rules file ./rules/evdev...
... Trying to load rules file /usr/share/X11/xkb/rules/evdev...
... Success.
... Applied rules from evdev:
... rules:      evdev
... model:      pc105
... layout:     semimak-jq
... options:    terminate:ctrl_alt_bksp
... Trying to build keymap using the following components:
... keycodes:   evdev+aliases(qwerty)
... types:      complete
... compat:     complete
... symbols:    pc+semimak-jq+inet(evdev)+terminate(ctrl_alt_bksp)
... geometry:   pc(pc105)
... rules:      evdev
... model:      pc105
... layout:     semimak-jq
... options:    terminate:ctrl_alt_bksp
... """
>>> kb_layout_regex = re.compile(r"layout:\s+(?P<layout>\w+)")
>>> kb_layout_regex.search(s)
<re.Match object; span=(215, 234), match='layout:     semimak'>
>>> match_ = kb_layout_regex.search(s)
>>> board = match_.group("layout")
>>> board
'semimak'
>>> new_kb_layout_regex = re.compile(r"layout:\s+(?P<layout>[\w-]+)")
>>> match_2 = new_kb_layout_regex.search(s)
>>> board2 = match_2.group("layout")
>>> board2
'semimak-jq'
>>>
`

@elParaguayo shall I create and open a PR for this?
Yes please.

Resolving patch:
diff --git a/libqtile/widget/keyboardlayout.py b/libqtile/widget/keyboardlayout.py
--- a/libqtile/widget/keyboardlayout.py
+++ b/libqtile/widget/keyboardlayout.py
@@ -60,7 +60,7 @@ def set_keyboard(self, layout: str, options: str | None) -> None:
 
 
 class _X11LayoutBackend(_BaseLayoutBackend):
-    kb_layout_regex = re.compile(r"layout:\s+(?P<layout>\w+)")
+    kb_layout_regex = re.compile(r"layout:\s+(?P<layout>[\w-]+)")
     kb_variant_regex = re.compile(r"variant:\s+(?P<variant>\w+)")
 
     def get_keyboard(self) -> str:


Test patch: