102 lines
4.0 KiB
Python
102 lines
4.0 KiB
Python
"""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()
|