|
@@ -155,38 +155,6 @@ def x_find_window_by_pid(display, pid):
|
|
|
return x_find_window(display.screen().root, filter_callback)
|
|
|
|
|
|
|
|
|
-def x_grab_key(grab_window, keycode, modifiers=None):
|
|
|
- if modifiers is None:
|
|
|
- modifiers = X.AnyModifier
|
|
|
- grab_window.grab_key(
|
|
|
- keycode,
|
|
|
- modifiers,
|
|
|
- # owner_events
|
|
|
- # https://stackoverflow.com/questions/32122360/x11-will-xgrabpointer-prevent-other-apps-from-any-mouse-event
|
|
|
- # False,
|
|
|
- True,
|
|
|
- X.GrabModeAsync,
|
|
|
- X.GrabModeAsync,
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-def x_grab_keysym(xdisplay, grab_window, keysym, modifiers=None):
|
|
|
- x_grab_key(
|
|
|
- grab_window=grab_window,
|
|
|
- keycode=xdisplay.keysym_to_keycode(keysym),
|
|
|
- modifiers=modifiers,
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-def x_ungrab_keysym(xdisplay, grab_window, keysym, modifiers=None):
|
|
|
- if modifiers is None:
|
|
|
- modifiers = X.AnyModifier
|
|
|
- grab_window.ungrab_key(
|
|
|
- xdisplay.keysym_to_keycode(keysym),
|
|
|
- modifiers,
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
def wait_for_engine_window(xdisplay, engine_process, timeout_seconds=20, search_interval_seconds=2):
|
|
|
start_epoch = time.time()
|
|
|
while engine_process.poll() is None and (time.time() - start_epoch) <= timeout_seconds:
|
|
@@ -200,23 +168,97 @@ def wait_for_engine_window(xdisplay, engine_process, timeout_seconds=20, search_
|
|
|
|
|
|
class ExtendedControls:
|
|
|
|
|
|
- def __init__(self, xdisplay, engine_window, keyboard_mapping, toggle_keysym_name):
|
|
|
- self._xdisplay = xdisplay
|
|
|
- self._engine_window = engine_window
|
|
|
+ def __init__(self, engine_process, toggle_keysym_name):
|
|
|
+ if not Xlib:
|
|
|
+ raise Exception('\n'.join([
|
|
|
+ 'Extended keyboard controls require xlib for python to be installed.',
|
|
|
+ 'Depending on your system run',
|
|
|
+ '\t$ sudo apt-get install python3-xlib',
|
|
|
+ 'or',
|
|
|
+ '\t$ pip3 install --user xlib',
|
|
|
+ ]))
|
|
|
+ self._engine_process = engine_process
|
|
|
+ self._xdisplay = Xlib.display.Display()
|
|
|
+ self._toggle_keysym = XK.string_to_keysym(toggle_keysym_name)
|
|
|
+ if self._toggle_keysym == X.NoSymbol:
|
|
|
+ raise Exception("Extended keyboard controls toggle:"
|
|
|
+ + " Unknown keysym name '{}'".format(toggle_keysym_name))
|
|
|
+ keyboard_mapping = copy.deepcopy(
|
|
|
+ EXTENDED_KEYBOARD_CONTROLS_DEFAULT_MAPPING
|
|
|
+ )
|
|
|
+ if self._toggle_keysym in keyboard_mapping:
|
|
|
+ del keyboard_mapping[self._toggle_keysym]
|
|
|
+ print("INFO Extended Controls:"
|
|
|
+ + " Ignoring mapping for toggle key '{}'".format(toggle_keysym_name))
|
|
|
self._keyboard_mapping = keyboard_mapping
|
|
|
+ self._engine_window = None
|
|
|
self._enabled = False
|
|
|
- print("INFO Extended Controls are currently off."
|
|
|
- + " Press key '{}' to enable.".format(toggle_keysym_name))
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ self._engine_window = wait_for_engine_window(
|
|
|
+ self._xdisplay,
|
|
|
+ self._engine_process,
|
|
|
+ )
|
|
|
+ if not self._engine_window:
|
|
|
+ raise Exception('Could not find the game\'s window.')
|
|
|
+ self._grab_key(
|
|
|
+ self._xdisplay.keysym_to_keycode(self._toggle_keysym),
|
|
|
+ )
|
|
|
+ if not self.enabled:
|
|
|
+ keysym_name = XK.keysym_to_string(self._toggle_keysym)
|
|
|
+ print("INFO Extended Controls are currently disabled."
|
|
|
+ + " Press key '{}' to enable.".format(keysym_name))
|
|
|
+ while self._engine_process.poll() is None:
|
|
|
+ # TODO don't block here, engine might have already been stopped
|
|
|
+ self._handle_xevent(self._xdisplay.next_event())
|
|
|
+
|
|
|
+ def _handle_xevent(self, xevent):
|
|
|
+ # TODO investigate why some release events get lost
|
|
|
+ if isinstance(xevent, Xlib.protocol.event.KeyPress) \
|
|
|
+ or isinstance(xevent, Xlib.protocol.event.KeyRelease):
|
|
|
+ self._handle_xkeyevent(xevent)
|
|
|
+
|
|
|
+ def _handle_xkeyevent(self, xkeyevent):
|
|
|
+ # TODO map keycodes instead of keysyms
|
|
|
+ keysym_in = self._xdisplay.keycode_to_keysym(
|
|
|
+ xkeyevent.detail,
|
|
|
+ index=0,
|
|
|
+ )
|
|
|
+ if keysym_in == self._toggle_keysym:
|
|
|
+ if isinstance(xkeyevent, Xlib.protocol.event.KeyPress):
|
|
|
+ self.toggle()
|
|
|
+ else:
|
|
|
+ if self.enabled and keysym_in in self._keyboard_mapping:
|
|
|
+ keysym_out = self._keyboard_mapping[keysym_in]
|
|
|
+ else:
|
|
|
+ keysym_out = keysym_in
|
|
|
+ self._engine_window.send_event(type(xkeyevent)(
|
|
|
+ window=self._engine_window,
|
|
|
+ detail=self._xdisplay.keysym_to_keycode(keysym_out),
|
|
|
+ state=0,
|
|
|
+ root_x=xkeyevent.root_x,
|
|
|
+ root_y=xkeyevent.root_y,
|
|
|
+ event_x=xkeyevent.event_x,
|
|
|
+ event_y=xkeyevent.event_y,
|
|
|
+ child=xkeyevent.child,
|
|
|
+ root=xkeyevent.root,
|
|
|
+ time=xkeyevent.time, # X.CurrentTime
|
|
|
+ same_screen=xkeyevent.same_screen,
|
|
|
+ ))
|
|
|
|
|
|
def enable(self):
|
|
|
for keysym in self._keyboard_mapping.keys():
|
|
|
- x_grab_keysym(self._xdisplay, self._engine_window, keysym)
|
|
|
+ self._grab_key(
|
|
|
+ self._xdisplay.keysym_to_keycode(keysym),
|
|
|
+ )
|
|
|
self._enabled = True
|
|
|
print("INFO Enabled Extended Controls")
|
|
|
|
|
|
def disable(self):
|
|
|
for keysym in self._keyboard_mapping.keys():
|
|
|
- x_ungrab_keysym(self._xdisplay, self._engine_window, keysym)
|
|
|
+ self._ungrab_key(
|
|
|
+ self._xdisplay.keysym_to_keycode(keysym),
|
|
|
+ )
|
|
|
self._enabled = False
|
|
|
print("INFO Disabled Extended Controls")
|
|
|
|
|
@@ -230,68 +272,20 @@ class ExtendedControls:
|
|
|
else:
|
|
|
self.enable()
|
|
|
|
|
|
-def run_extended_keyboard_controls(engine_process, toggle_keysym_name):
|
|
|
- if not Xlib:
|
|
|
- raise Exception('\n'.join([
|
|
|
- 'Extended keyboard controls require xlib for python to be installed.',
|
|
|
- 'Depending on your system run',
|
|
|
- '\t$ sudo apt-get install python3-xlib',
|
|
|
- 'or',
|
|
|
- '\t$ pip3 install --user xlib',
|
|
|
- ]))
|
|
|
- xdisplay = Xlib.display.Display()
|
|
|
- engine_window = wait_for_engine_window(xdisplay, engine_process)
|
|
|
- if not engine_window:
|
|
|
- raise Exception('Could not find the game\'s window.')
|
|
|
- toggle_keysym = XK.string_to_keysym(toggle_keysym_name)
|
|
|
- if toggle_keysym == X.NoSymbol:
|
|
|
- raise Exception("Extended keyboard controls toggle:"
|
|
|
- + " Unknown keysym name '{}'".format(toggle_keysym_name))
|
|
|
- x_grab_keysym(xdisplay, engine_window, toggle_keysym)
|
|
|
- keyboard_mapping = copy.deepcopy(
|
|
|
- EXTENDED_KEYBOARD_CONTROLS_DEFAULT_MAPPING
|
|
|
- )
|
|
|
- if toggle_keysym in keyboard_mapping:
|
|
|
- del keyboard_mapping[toggle_keysym]
|
|
|
- print("INFO Extended Controls:"
|
|
|
- + " Ignoring mapping for toggle key '{}'".format(toggle_keysym_name))
|
|
|
- ec = ExtendedControls(
|
|
|
- xdisplay,
|
|
|
- engine_window,
|
|
|
- keyboard_mapping,
|
|
|
- toggle_keysym_name,
|
|
|
- )
|
|
|
- while engine_process.poll() is None:
|
|
|
- # TODO don't block here, engine might have already been stopped
|
|
|
- xevent = xdisplay.next_event()
|
|
|
- # TODO investigate why some release events get lost
|
|
|
- if isinstance(xevent, Xlib.protocol.event.KeyPress) \
|
|
|
- or isinstance(xevent, Xlib.protocol.event.KeyRelease):
|
|
|
- keysym_in = xdisplay.keycode_to_keysym(
|
|
|
- xevent.detail,
|
|
|
- index=0,
|
|
|
- )
|
|
|
- if keysym_in == toggle_keysym:
|
|
|
- if isinstance(xevent, Xlib.protocol.event.KeyPress):
|
|
|
- ec.toggle()
|
|
|
- else:
|
|
|
- if ec.enabled and keysym_in in keyboard_mapping:
|
|
|
- keysym_out = keyboard_mapping[keysym_in]
|
|
|
- else:
|
|
|
- keysym_out = keysym_in
|
|
|
- engine_window.send_event(type(xevent)(
|
|
|
- window=engine_window,
|
|
|
- detail=xdisplay.keysym_to_keycode(keysym_out),
|
|
|
- state=0,
|
|
|
- root_x=xevent.root_x,
|
|
|
- root_y=xevent.root_y,
|
|
|
- event_x=xevent.event_x,
|
|
|
- event_y=xevent.event_y,
|
|
|
- child=xevent.child,
|
|
|
- root=xevent.root,
|
|
|
- time=xevent.time, # X.CurrentTime
|
|
|
- same_screen=xevent.same_screen,
|
|
|
- ))
|
|
|
+ def _grab_key(self, keycode):
|
|
|
+ self._engine_window.grab_key(
|
|
|
+ keycode,
|
|
|
+ X.AnyModifier,
|
|
|
+ # owner_events
|
|
|
+ # https://stackoverflow.com/questions/32122360/x11-will-xgrabpointer-prevent-other-apps-from-any-mouse-event
|
|
|
+ # False,
|
|
|
+ True,
|
|
|
+ X.GrabModeAsync,
|
|
|
+ X.GrabModeAsync,
|
|
|
+ )
|
|
|
+
|
|
|
+ def _ungrab_key(self, keycode):
|
|
|
+ self._engine_window.ungrab_key(keycode, X.AnyModifier)
|
|
|
|
|
|
|
|
|
def launch(engine_path, username, password, validate_ssl_certs=True,
|
|
@@ -322,10 +316,10 @@ def launch(engine_path, username, password, validate_ssl_certs=True,
|
|
|
])
|
|
|
if enable_extended_keyboard_controls:
|
|
|
try:
|
|
|
- run_extended_keyboard_controls(
|
|
|
+ ExtendedControls(
|
|
|
engine_process=p,
|
|
|
toggle_keysym_name=extended_keyboard_control_toggle_keysym_name,
|
|
|
- )
|
|
|
+ ).run()
|
|
|
except Exception as e:
|
|
|
if isinstance(e, KeyboardInterrupt):
|
|
|
raise e
|