"""Private Xorg key sink: focus, delivered keys and conflicting passive grabs.""" import json import os import select import sys from Xlib import X, Xatom, XK, display from Xlib.ext import xtest def main(): connection = display.Display() root = connection.screen().root activeAtom = connection.intern_atom('_NET_ACTIVE_WINDOW') previousActive = root.get_full_property(activeAtom, X.AnyPropertyType) pidAtom = connection.intern_atom('_NET_WM_PID') keys = [] def make_window(x): window = root.create_window(x, 30, 300, 200, 0, connection.screen().root_depth, X.InputOutput, X.CopyFromParent, background_pixel=0xFFFFFF, event_mask=X.KeyPressMask | X.KeyReleaseMask) window.set_wm_name('OCR review fixture') window.set_wm_class('example', 'example') window.change_property(pidAtom, Xatom.CARDINAL, 32, [os.getpid()]) window.map() return window window = make_window(40) other = make_window(380) competitor = display.Display() competitorRoot = competitor.screen().root conflictCode = competitor.keysym_to_keycode(XK.string_to_keysym('c')) def activate(target): root.change_property(activeAtom, Xatom.WINDOW, 32, [target.id]) target.set_input_focus(X.RevertToParent, X.CurrentTime) def drain(): connection.sync() while connection.pending_events(): event = connection.next_event() if event.type in (X.KeyPress, X.KeyRelease): keys.append(dict(pressed=event.type == X.KeyPress, symbol=connection.keycode_to_keysym(event.detail, 0), modifiers=event.state, window=event.window.id)) activate(window) connection.sync() print(json.dumps(dict(window=window.id, other=other.id)), flush=True) try: while True: drain() readable, _, _ = select.select([sys.stdin, connection.fileno()], [], []) if sys.stdin not in readable: continue line = sys.stdin.readline() if not line: break command = json.loads(line) action = command['action'] if action == 'activate': activate(other if command.get('other') else window) elif action == 'key': keyCode = connection.keysym_to_keycode(XK.string_to_keysym(command['name'])) if not keyCode: raise ValueError('Unknown fixture key') xtest.fake_input(connection, X.KeyPress, keyCode) xtest.fake_input(connection, X.KeyRelease, keyCode) elif action == 'class': window.set_wm_class(command['name'], command['name']) elif action == 'unknown': root.change_property(activeAtom, Xatom.WINDOW, 32, [X.NONE]) elif action == 'conflict': competitorRoot.grab_key(conflictCode, X.ControlMask, False, X.GrabModeAsync, X.GrabModeAsync) competitor.sync() elif action == 'unconflict': competitorRoot.ungrab_key(conflictCode, X.ControlMask) competitor.sync() elif action not in ('state', 'clear'): raise ValueError('unknown fixture action') drain() if action == 'clear': keys.clear() print(json.dumps(dict(focus=connection.get_input_focus().focus.id, keys=keys)), flush=True) finally: if previousActive is None: root.delete_property(activeAtom) else: root.change_property(activeAtom, previousActive.property_type, previousActive.format, previousActive.value) connection.sync() competitor.close() connection.close() if __name__ == '__main__': main()