pynput icon indicating copy to clipboard operation
pynput copied to clipboard

Issue with release shift on Linux Mint 19

Open OlegYurchik opened this issue 4 years ago • 5 comments

When I press left shift, pressed shift Key, but when I release left shift, <65032> KeyCode released.

When I press right shift, pressed shift_r Key, but when I release right shift, <65032> released.

class KeyLogger(keyboard.Listener):
    def __init__(self):
        super().__init__(on_press=self.on_press, on_release=self.on_release)

        self.pressed = set()

    def on_press(self, key):
        print("PRESS:", key)
        self.pressed.add(key)

    def on_release(self, key):
        print("RELEASE:", key)
        self.pressed.remove(key)

When I press and release left shift, I got this:

PRESS shift
RELEASE <65032>

OlegYurchik avatar Jan 08 '20 20:01 OlegYurchik

That is strange...

Could you please re-run you sample with the following patch applied?

iff --git a/lib/pynput/keyboard/_base.py b/lib/pynput/keyboard/_base.py
index ce2ac3d..9cbb1b1 100644
--- a/lib/pynput/keyboard/_base.py
+++ b/lib/pynput/keyboard/_base.py
@@ -69,7 +69,9 @@ class KeyCode(object):
         if self.char is not None:
             return repr(self.char)
         else:
-            return '<%d>' % self.vk
+            return '<%d {%s}>' % (self.vk, ', '.join(
+                '{}={}'.format(name[1:], getattr(self, name)
+                for name in self._PLATFORM_EXTENSIONS)))
 
     def __str__(self):
         return repr(self)
diff --git a/lib/pynput/keyboard/_xorg.py b/lib/pynput/keyboard/_xorg.py
index 0734ad0..f5cbe0c 100644
--- a/lib/pynput/keyboard/_xorg.py
+++ b/lib/pynput/keyboard/_xorg.py
@@ -625,9 +625,10 @@ class Listener(ListenerMixin, _base.Listener):
         if name is not None and name in SYMBOLS:
             char = SYMBOLS[name][1].upper() if index & 1 else SYMBOLS[name][1]
             if char in DEAD_KEYS:
-                return KeyCode.from_dead(DEAD_KEYS[char], vk=keysym)
+                return KeyCode.from_dead(
+                        DEAD_KEYS[char], vk=keysym, _symbol=name)
             else:
-                return KeyCode.from_char(char, vk=keysym)
+                return KeyCode.from_char(char, vk=keysym, _symbol=name)
 
         # ...and fall back on a virtual key code
-        return KeyCode.from_vk(keysym)
+        return KeyCode.from_vk(keysym, _symbol=name)

moses-palmer avatar Jan 09 '20 16:01 moses-palmer

I have the same issue as detailed above, specifically using Peppermint 10. One difference for my system is it only happens for left shift, right shift works fine.

I tried implementing the above fix but it didn't work, shift in on_release still returns as <65032>.

A possibly interesting note is that the object returned for left shift on release is a KeyCode rather than a Key

AngryMajor avatar Feb 28 '20 16:02 AngryMajor

Same here on both shift keys on Ubuntu 20.04 ... seems like a bug somewhere down the chain.

rubmz avatar Apr 16 '21 22:04 rubmz

I encountered the same problem on my Kubuntu 20.04 and I am pretty sure I know what causes this. What causes the incorrect keycode is

def _keycode_to_keysym(self, display, keycode, index):
    <...>
    keysym = display.keycode_to_keysym(keycode, index)

in _xorg.py

display.keycode_to_keysym(keycode, index) gets keycode from Xlib, Xlib searches the keycode in data it gets directly from X, which in my case looks like this:

...
50: array('I', [65505, 65032, 65505, 65032, 0, 0, 0, 0, 0, 0]),
...

What is going on? Pynput queries element number 0 from array above if shift is not pressed, and element number 1 if shift is pressed. This logic works well for capitalized letters, but fails for shift key itself. When key_press event fires, shift is not yet pressed, so pynput queries element number 0, 65505. When key_release event fires, shift is pressed at the moment, so pynput queries element number 1: 65032 aka ISO_Next_Group:

xmodmap -pk | grep Shift_L
     50         0xffe1 (Shift_L)        0xfe08 (ISO_Next_Group) 0xffe1 (Shift_L)        0xfe08 (ISO_Next_Group)

(0xfe08 is 65032)

Fixing this without changing the baseline logic of the library could be problematical, I mean, pynput doesn't know what keys it is querying codes for. So we can't just add something like "if key is shift, send index 0 even when it is 1". Pynput doesn't know the key is actually shift at the moment of request, if I got it right. I would be really happy if I am mistaken.

What could be done: If you encounter this on your system, you may sacrifice ISO_Next_Group, e.g. xmodmap -e "keycode 50 = Shift_L Shift_L Shift_L Shift_L" or add keycode 50 = Shift_L Shift_L Shift_L Shift_L to .Xmodmap in ~/ directory.

But if you do this, switching the keyboard layout with Alt+Shift (Windows-style) in KDE is no longer possible.

This is not an option for me so I went for a crutch-patch, changing _keycode_to_keysym in _xorg.py:

    def _keycode_to_keysym(self, display, keycode, index):
        """Converts a keycode and shift state index to a keysym.

        This method uses a simplified version of the *X* convention to locate
        the correct keysym in the display table: since this method is only used
        to locate special keys, alphanumeric keys are not treated specially.

        :param display: The current *X* display.

        :param keycode: The keycode.

        :param index: The shift state index.

        :return: a keysym
        """
-       keysym = display.keycode_to_keysym(keycode, index)
+       if keycode == 50:
+           keysym = 65505 # LShift
+       elif keycode == 62:
+           keysym = 65506 # RShift
+       else:
+           keysym = display.keycode_to_keysym(keycode, index)
        
        if keysym:
            return keysym
        elif index & 0x2:
            return self._keycode_to_keysym(display, keycode, index & ~0x2)
        elif index & 0x1:
            return self._keycode_to_keysym(display, keycode, index & ~0x1)
        else:
            return 0

Edit To reproduce this issue, bind something to the second and fourth levels of Shift that is not Shift, e.g.

xmodmap -e "keycode 50 = Shift_L ISO_Next_Group Shift_L ISO_Next_Group"
xmodmap -e "keycode 62 = Shift_R ISO_Next_Group Shift_R ISO_Next_Group"

IGotPaws avatar Oct 28 '21 11:10 IGotPaws

Another issue strongly connected with this is:

  1. Press any key with a symbol and hold it, let's say "J".
  2. Press shift and hold
  3. Release J
  4. Release shift

Expectation:

  1. Keycode 106 keydown event
  2. Keycode 65505 keydown event
  3. Keycode 106 release event
  4. Keycode 65505 release event

Reality:

PRESSED>  106
PRESSED>  65505
PRESSED>  74
RELEASED>  74
RELEASED>  65505

Reasons are the same, at the moment J is released, pynput requests the keycode for shifted J. What is interesting there is the third line, keycode 74 keydown event (shifted J) is fired at the moment shift is being pressed. So pressing shift fires two events here.

Edit I ended up doing this in _xorg.py

    def _handle(self, display, event):
        # Convert the event to a KeyCode; this may fail, and in that case we
        # pass None
        try:
            key = self._event_to_key(display, event)
        except IndexError:
            key = None

        if event.type == Xlib.X.KeyPress:
-            self.on_press(key)
+            self.on_press(key, event.detail)

        elif event.type == Xlib.X.KeyRelease:
-            self.on_release(key)
+            self.on_release(key, event.detail)

event.detail contains the original X key code, e.g. 50 for shift, etc Passing this code to onpress and onrelease events allows me to work with them now instead of Xkb codes, which satisfies my needs at the time:

PRESSED>  44
PRESSED>  50
RELEASED>  44
RELEASED>  50

If you encounter the same problem, you might want to do the same.

IGotPaws avatar Oct 29 '21 01:10 IGotPaws