From 64b077bd018ad6fe8dc1a627e7859bcbe17ce640 Mon Sep 17 00:00:00 2001 From: Si0uL <26586210+Si0uL@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:48:20 +0200 Subject: [PATCH] Add AltGr support on Linux systems to allow Eu special chars handling --- lib/pynput/_util/xorg.py | 24 ++++++++++++++++++++---- lib/pynput/keyboard/_xorg.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/pynput/_util/xorg.py b/lib/pynput/_util/xorg.py index 66b5b21a..da013150 100644 --- a/lib/pynput/_util/xorg.py +++ b/lib/pynput/_util/xorg.py @@ -27,6 +27,7 @@ import operator import Xlib.display import Xlib.keysymdef +import Xlib.keysymdef.xkb import Xlib.threaded import Xlib.XK @@ -94,9 +95,21 @@ def _find_mask(display, symbol): :return: the modifier mask """ # Get the key code for the symbol - modifier_keycode = display.keysym_to_keycode( - Xlib.XK.string_to_keysym(symbol) - ) + keysym = Xlib.XK.string_to_keysym(symbol) + if not keysym: + # pylint: disable=W0702; we want to ignore errors + try: + keysym = getattr(Xlib.keysymdef.xkb, 'XK_' + symbol, 0) + except: + pass + # pylint: enable=W0702 + + if not keysym: + return 0 + + modifier_keycode = display.keysym_to_keycode(keysym) + if not modifier_keycode: + return 0 for index, keycodes in enumerate(display.get_modifier_mapping()): for keycode in keycodes: @@ -132,7 +145,10 @@ def alt_gr_mask(display): :return: the modifier mask """ if not hasattr(display, '__altgr_mask'): - display.__altgr_mask = _find_mask(display, 'Mode_switch') + mask = _find_mask(display, 'Mode_switch') + if not mask: + mask = _find_mask(display, 'ISO_Level3_Shift') + display.__altgr_mask = mask return display.__altgr_mask diff --git a/lib/pynput/keyboard/_xorg.py b/lib/pynput/keyboard/_xorg.py index 4dc5203a..3f1aed47 100644 --- a/lib/pynput/keyboard/_xorg.py +++ b/lib/pynput/keyboard/_xorg.py @@ -208,6 +208,16 @@ def __init__(self, *args, **kwargs): self.ALT_GR_MASK = alt_gr_mask(self._display) # pylint: enable=C0103 + if Key.alt_gr.value.vk: + try: + import Xlib.keysymdef.xkb as _xkb + if not self._display.keysym_to_keycode(Key.alt_gr.value.vk): + iso_vk = getattr(_xkb, 'XK_ISO_Level3_Shift', 0) + if iso_vk and self._display.keysym_to_keycode(iso_vk): + Key.alt_gr.value.vk = iso_vk + except: + pass + def __del__(self): if hasattr(self, '_display'): self._display.close() @@ -518,6 +528,12 @@ class Listener(ListenerMixin, _base.Listener): #: A mapping from keysym to special key _SPECIAL_KEYS = {key.value.vk: key for key in Key} + try: + import Xlib.keysymdef.xkb as _xkb + if hasattr(_xkb, 'XK_ISO_Level3_Shift'): + _SPECIAL_KEYS[_xkb.XK_ISO_Level3_Shift] = Key.alt_gr + except (ImportError, AttributeError): + pass #: A mapping from numeric keypad keys to keys _KEYPAD_KEYS = { @@ -565,6 +581,17 @@ def _run(self): super(Listener, self)._run() def _initialize(self, display): + # Ensure Key.alt_gr.value.vk matches the active system keysym for AltGr + if Key.alt_gr.value.vk: + try: + import Xlib.keysymdef.xkb as _xkb + if not display.keysym_to_keycode(Key.alt_gr.value.vk): + iso_vk = getattr(_xkb, 'XK_ISO_Level3_Shift', 0) + if iso_vk and display.keysym_to_keycode(iso_vk): + Key.alt_gr.value.vk = iso_vk + except: + pass + # Get the keyboard mapping to be able to translate event details to # key codes min_keycode = display.display.info.min_keycode @@ -624,6 +651,15 @@ def _keycode_to_keysym(self, display, keycode, index): :return: a keysym """ + if index & 0x2 and self._keyboard_mapping is not None: + try: + min_keycode = display.display.info.min_keycode + keysyms = self._keyboard_mapping[keycode - min_keycode] + if len(keysyms) >= 6 and keysyms[index + 2] != 0: + return keysyms[index + 2] + except (IndexError, TypeError): + pass + keysym = display.keycode_to_keysym(keycode, index) if keysym: return keysym