Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python xlib xgrabkey keyrelease events not firing

Tags:

python

input

xlib

i want to catch keydown and keyup events with python xlib, but keyup events disappear when some keys are pressed simultaneously.

if 2 or more keyes are released simultaneously then there will be 2 or more keypress events, but only 1 keyrelease event.

for this to happen the keys don't even have to be released simultaneously, for example if you enter this sequence fast:

  1. press A
  2. press B
  3. release A
  4. release B

will yield only 1 keyrelease for A

  1. press A
  2. press B
  3. release B
  4. release A

will yield 2 keyreleases

from Xlib import X,XK
from Xlib.display import Display
import signal,sys

root = None
display = None

def grab_keyname(n):
    global root
    keysym = XK.string_to_keysym(n)
    keycode = display.keysym_to_keycode(keysym)
    root.grab_key(keycode, X.AnyModifier, False,X.GrabModeSync, X.GrabModeAsync)

def main():
    # current display
    global display,root
    display = Display()
    root = display.screen().root


    root.change_attributes(event_mask = X.KeyPressMask|X.KeyReleaseMask)

    grab_keyname("j")
    grab_keyname("k")
    grab_keyname("l")

    signal.signal(signal.SIGALRM, lambda a,b:sys.exit(1))
    signal.alarm(4)

    while True:
        event = display.next_event()
        print event.type

main()
like image 537
tino Avatar asked Oct 21 '22 03:10

tino


1 Answers

Even though this question is 7 years old, a solution for anyone stumbling upon this:

This is a 'bug' in Xorg (which is apparently intentional) which causes keyboard grab to stop upon a key release, and only to start again on another button press. Therefore, any events in between (-> the second key release event) are lost. See https://bugs.freedesktop.org/show_bug.cgi?id=99280

The solution proposed there is to have a counter which indicates how many of the keys are still pressed, and manually grab the keyboard if there are still pending presses. In addition, the keyboard needs to be ungrabbed after the last release event.

The implementation might look like the following:

keys_pressed = 0
# ...
event = display.next_event()
if event.type == X.KeyPress:
    keys_pressed += 1
elif event.type == X.KeyRelease:
    if keys_pressed != 0:
        keys_pressed -= 1
        if keys_pressed == 0:
            display.flush()
            display.ungrab_keyboard(X.CurrentTime)
        else:
            root.grab_keyboard(False, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)

Note the check for keys_pressed != 0 in the release branch: this is because after ungrabbing the keyboard, one additional release event is caught (there is probably a way to prevent this, but it does not matter for my use case...)

like image 168
Lukor Avatar answered Oct 24 '22 04:10

Lukor