Skip to content

Incorrect layout when reporting keys with xorg backend #671

Description

@vxgmichel

Let's say I have two keyboard layouts configured in Xorg.
If I use the second layout, pynput still reports key presses using the first layout.
This is because pynput doesn't use the layout group index (bits 13 and 14 of event.state):

Normally, the Xkb-aware server reports keyboard state in the state member of events such as a KeyPress event and ButtonPress event, encoded as follows:
bits meaning
15 0
13-14 Group index
8-12 Pointer Buttons
0-7 Modifiers

https://www.x.org/releases/X11R7.7/doc/libX11/XKB/xkblib.html

I'm not sure about the proper way to compute the layout index, but I managed to get something that seems to work using this diff:

diff --git a/lib/pynput/keyboard/_xorg.py b/lib/pynput/keyboard/_xorg.py
index 7011a5a..a4d99a1 100644
--- a/lib/pynput/keyboard/_xorg.py
+++ b/lib/pynput/keyboard/_xorg.py
@@ -635,7 +635,21 @@ class Listener(ListenerMixin, _base.Listener):
         :raises IndexError: if the key code is invalid
         """
         keycode = event.detail
-        index = shift_to_index(display, event.state)
+
+        from pynput._util.xorg import _find_mask
+        shift_mask = Xlib.X.ShiftMask
+        alt_gr_mask = _find_mask(display, 'ISO_Level3_Shift') or _find_mask(display, 'Mode_switch')
+        shift = 1 if event.state & shift_mask else 0
+        alt_gr = 2 if event.state & alt_gr_mask else 0
+        group = 4 * ((event.state >> 13) & 0x03)
+        index = group | alt_gr | shift
+        syms = len(display.get_keyboard_mapping(keycode, 1)[0])
+        # Swap slots 2-3 and 4-5
+        if syms > 4:
+            if index in (2, 3):
+                index += 2
+            elif index in (4, 5):
+                index -= 2
 
         # First try special keys...
         keysym = self._keycode_to_keysym(display, keycode, index)

The swapping might look silly but this how the layouts are organized, somehow.
For instance let's say I have 3 layouts configured in this specific order:

  • US (q maps to q, Q, ä, Ä)
  • BEPO (q maps to b, B, |, ¦)
  • FR (q maps to a, A, æ, Æ)
    Then display._keymap_codes[keycode] for q contains:
0: 'q',
1: 'Q',
2: 'b',
3: 'B',
4: 'ä',
5: 'Ä',
6: '|',
7: '¦',
8: 'a',
9: 'A',
10: 'æ',
11: 'Æ',
12: 'q',
13: 'Q',
14: '\x00',
15: '\x00',
16: '\x00',
17: '\x00',
18: '\x00',
19: '\x00',

Also note that I had to use 'ISO_Level3_Shift' instead of 'Mode_switch' in order to get the correct alt_gr_mask.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions