Repository: ultrabug/py3status

Issue:
keyboard_layout: keyboard shortcut stop working
Problem
--------------------

Switching keyboard layout causes layout switching shortcut to stop working (This might related to #1742)


Analysis
---------------------

[`_set_active`](https://github.com/ultrabug/py3status/blob/65a609261095f7502fc2814e9a6feb478845a69c/py3status/modules/keyboard_layout.py#L159-L163) switch layout by replacing all layout in `XkbLayout` with the target layout, This causes switching layout shortcut to not work since there's only a single layout left.

Currently, It's not possible to choose layout without replacing `XkbLayout` through `setxkbmap`,
Fortunately, `xkblayout-state` provides a proper method to switch layout.

Will submit PR later.


Discussion:

Resolving patch:
diff --git a/py3status/modules/keyboard_layout.py b/py3status/modules/keyboard_layout.py
--- a/py3status/modules/keyboard_layout.py
+++ b/py3status/modules/keyboard_layout.py
@@ -18,7 +18,7 @@
 Requires:
     xkblayout-state:
         or
-    setxkbmap: and `xset` (works for the first two predefined layouts.)
+    setxkbmap: and `xset` (works for the first two predefined layouts. overrides `XkbLayout` when switching layout.)
 
 Examples:
 ```
@@ -68,11 +68,14 @@ class Py3status:
 
     def post_config_hook(self):
         self.colors = getattr(self, "colors", None)  # old config
-        try:
-            self._xkblayout()
-            self._command = self._xkblayout
-        except self.py3.CommandError:
-            self._command = self._setxkbmap
+
+        avail_command = {
+            "xkblayout-state": (self._set_xkblayout, self._get_xkblayout),
+            "setxkbmap": (self._set_setxkbmap, self._get_setxkbmap),
+        }
+        self._set_command, self._get_command = avail_command[
+            self.py3.check_commands(avail_command.keys())
+        ]
 
         if not self.layouts:
             self.layouts = []
@@ -91,7 +94,7 @@ def post_config_hook(self):
         }
 
     def keyboard_layout(self):
-        layout, variant = self._command()
+        layout, variant = self._get_command()
         # If the current layout is not in our layouts list we need to add it
         if layout not in self._layouts:
             self._layouts = [layout] + self.layouts
@@ -131,7 +134,7 @@ def keyboard_layout(self):
 
         return response
 
-    def _xkblayout(self):
+    def _get_xkblayout(self):
         layout, variant = [
             x.strip()
             for x in self.py3.command_output(
@@ -140,7 +143,7 @@ def _xkblayout(self):
         ]
         return layout, variant
 
-    def _setxkbmap(self):
+    def _get_setxkbmap(self):
         # this method works only for the first two predefined layouts.
         out = self.py3.command_output(["setxkbmap", "-query"])
         layouts = re.match(LAYOUTS_RE, out).group(1).split(",")
@@ -156,11 +159,24 @@ def _setxkbmap(self):
         led_mask = re.match(LEDMASK_RE, xset_output).groups(0)[0]
         return layouts[int(led_mask)], ""
 
+    def _set_setxkbmap(self):
+        layout = self._layouts[self._active]
+        # Note: This will override user-defined layout, keyboard shortcut won't work
+        self.py3.command_run("setxkbmap -layout {}".format(layout))
+
+    def _set_xkblayout(self):
+        layout = self._layouts[self._active]
+        layout_pos = (
+            self.py3.command_output(["xkblayout-state", "print", "%S"])
+            .split()
+            .index(layout)
+        )
+        self.py3.command_run("xkblayout-state set {}".format(layout_pos))
+
     def _set_active(self, delta):
         self._active += delta
         self._active = self._active % len(self._layouts)
-        layout = self._layouts[self._active]
-        self.py3.command_run("setxkbmap -layout {}".format(layout))
+        self._set_command()
 
     def on_click(self, event):
         button = event["button"]


Test patch: