Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

globally capture, ignore and send keyevents with python xlib, recognize fake input

i want to implement key chording on a normal keyboard and i thought i use python xlib. for this to work the program has to globally swallow all keyevents and only later allow them to go through.

my current test just grabs the "1" key. if this key is pressed it calls a handler which sends "x" to the focused window via xtest.fake_input. because im only grabbing the "1"-key, there shouldn't be a problem, right? but somehow the handler gets called again, because "x" was pressed. in fact, the moment i type "1" the program is listening to all keys. this could have something to do with calling

display.allow_events(X.ReplayKeyboard, X.CurrentTime)

after handling an event, but if i don't do this, everything freezes.

for the final program the change in the listening behaviour is not really relevant, but i have to be able to distinguish the fake events from the user events. to do this i'm just fast forwarding display.next_event(), but this isnt ideal, because the user could be typing at that exact moment and than those keystrokes would be lost.

i tried releasing the keygrab during sending and emptying the eventqueue via

display.flush()
display.sync()

but that doesn't do anything.

so, any idea how to recognize or ignore fake input events and why i'm suddenly listening to all keypresses (and releases)?

xlib is very frustrating.

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

display = None
root = None

def handle_event(aEvent):
    print "handle!"
    send_key("x")

def send_key(emulated_key):

    global display,root
    print "send key"
    # ungrabbing doesnt help
    root.ungrab_key(10,X.AnyModifier) 
    window = display.get_input_focus()._data["focus"]
    # Generate the correct keycode
    keysym = Xlib.XK.string_to_keysym(emulated_key)
    keycode = display.keysym_to_keycode(keysym)
    # Send a fake keypress via xtestaaa
    Xlib.ext.xtest.fake_input(window, Xlib.X.KeyPress, keycode)
    Xlib.ext.xtest.fake_input(window, Xlib.X.KeyRelease, keycode)
    display.flush()
    display.sync()
    # fast forward those two events,this seems a bit hacky, 
    # what if theres another keyevent coming in at that exact time?
    while display.pending_events():
        display.next_event()
    #
    root.grab_key(10, X.AnyModifier, True,X.GrabModeSync, X.GrabModeSync)

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

    # we tell the X server we want to catch keyPress event
    root.change_attributes(event_mask = X.KeyPressMask)
    # just grab the "1"-key for now
    root.grab_key(10, X.AnyModifier, True,X.GrabModeSync, X.GrabModeSync)
    # while experimenting everything could freeze, so exit after 10 seconds
    signal.signal(signal.SIGALRM, lambda a,b:sys.exit(1))
    signal.alarm(10)
    while 1:
        event = display.next_event()
        print "event"
        #if i dont call this, the whole thing freezes
        display.allow_events(X.ReplayKeyboard, X.CurrentTime)
        handle_event(event)

if __name__ == '__main__':
    main()
like image 310
tino Avatar asked Aug 04 '13 01:08

tino


1 Answers

i found the problem. im almost certain that xtest.fake_input does something weird, because when i send keypresses and -releases manually (with some code i found), it works

here is an example, that swallows only the "1"-key on keypress and sends "x" on keyrelease to the focused window:

from Xlib.display import Display
import Xlib
from Xlib import X
import Xlib.XK
import sys
import signal 
import time
display = None
root = None

def handle_event(event):
    print "handle!"
    if (event.type == X.KeyRelease):
        send_key("x")

# from http://shallowsky.com/software/crikey/pykey-0.1 
def send_key(emulated_key):
    shift_mask = 0 # or Xlib.X.ShiftMask
    window = display.get_input_focus()._data["focus"]
    keysym = Xlib.XK.string_to_keysym(emulated_key)
    keycode = display.keysym_to_keycode(keysym)
    event = Xlib.protocol.event.KeyPress(
        time = int(time.time()),
        root = root,
        window = window,
        same_screen = 0, child = Xlib.X.NONE,
        root_x = 0, root_y = 0, event_x = 0, event_y = 0,
        state = shift_mask,
        detail = keycode
        )
    window.send_event(event, propagate = True)
    event = Xlib.protocol.event.KeyRelease(
        time = int(time.time()),
        root = display.screen().root,
        window = window,
        same_screen = 0, child = Xlib.X.NONE,
        root_x = 0, root_y = 0, event_x = 0, event_y = 0,
        state = shift_mask,
        detail = keycode
        )
    window.send_event(event, propagate = True)

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

    # we tell the X server we want to catch keyPress event
    root.change_attributes(event_mask = X.KeyPressMask|X.KeyReleaseMask)
    # just grab the "1"-key for now
    root.grab_key(10, 0, True,X.GrabModeSync, X.GrabModeSync)

    signal.signal(signal.SIGALRM, lambda a,b:sys.exit(1))
    signal.alarm(10)
    while 1:
        event = display.next_event()
        print "event"
        handle_event(event)
        display.allow_events(X.AsyncKeyboard, X.CurrentTime)            

if __name__ == '__main__':
    main()
like image 154
tino Avatar answered Nov 20 '22 21:11

tino