Ver código fonte

move implementation of ExtendedControls to new file tooncher/controls.py

Fabian Peter Hammerle 7 anos atrás
pai
commit
6b831bcbd7
2 arquivos alterados com 167 adições e 166 exclusões
  1. 2 166
      tooncher/__init__.py
  2. 165 0
      tooncher/controls.py

+ 2 - 166
tooncher/__init__.py

@@ -1,19 +1,13 @@
-import copy
 import datetime
 import json
 import os
 import ssl
 import subprocess
 import sys
-import time
 import traceback
 import urllib.parse
 import urllib.request
-try:
-    import Xlib.display
-    from Xlib import X, XK
-except ImportError:
-    Xlib = False
+import tooncher.controls
 
 """
 official api documentation:
@@ -37,14 +31,6 @@ else:
     TOONTOWN_LIBRARY_PATH = None
     TOONTOWN_ENGINE_DEFAULT_PATH = None
 
-if Xlib:
-    EXTENDED_KEYBOARD_CONTROLS_DEFAULT_MAPPING = {
-        XK.XK_w: XK.XK_Up,
-        XK.XK_a: XK.XK_Left,
-        XK.XK_s: XK.XK_Down,
-        XK.XK_d: XK.XK_Right,
-    }
-
 
 def start_engine(engine_path, gameserver, playcookie, **kwargs):
     env = {
@@ -137,156 +123,6 @@ def login(username=None, password=None,
         raise Exception(repr(resp_data))
 
 
-def x_find_window(parent_window, filter_callback):
-    matching = []
-    for child_window in parent_window.query_tree().children:
-        if filter_callback(child_window):
-            matching.append(child_window)
-        matching += x_find_window(child_window, filter_callback)
-    return matching
-
-
-def x_find_window_by_pid(display, pid):
-    pid_prop = display.intern_atom('_NET_WM_PID')
-
-    def filter_callback(window):
-        prop = window.get_full_property(pid_prop, X.AnyPropertyType)
-        return prop and prop.value.tolist() == [pid]
-    return x_find_window(display.screen().root, filter_callback)
-
-
-class ExtendedControls:
-
-    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
-
-    def _wait_for_engine_window(self, timeout_seconds=20, search_interval_seconds=2):
-        start_epoch = time.time()
-        while self._engine_process.poll() is None and (time.time() - start_epoch) <= timeout_seconds:
-            windows = x_find_window_by_pid(
-                self._xdisplay,
-                self._engine_process.pid,
-            )
-            assert len(windows) <= 1
-            if len(windows) == 1:
-                return windows[0]
-            time.sleep(search_interval_seconds)
-        return None
-
-    def run(self):
-        self._engine_window = self._wait_for_engine_window()
-        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():
-            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():
-            self._ungrab_key(
-                self._xdisplay.keysym_to_keycode(keysym),
-            )
-        self._enabled = False
-        print("INFO Disabled Extended Controls")
-
-    @property
-    def enabled(self):
-        return self._enabled
-
-    def toggle(self):
-        if self.enabled:
-            self.disable()
-        else:
-            self.enable()
-
-    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,
            cpu_limit_percent=None, enable_extended_keyboard_controls=False,
            extended_keyboard_control_toggle_keysym_name=None):
@@ -315,7 +151,7 @@ def launch(engine_path, username, password, validate_ssl_certs=True,
             ])
         if enable_extended_keyboard_controls:
             try:
-                ExtendedControls(
+                tooncher.controls.ExtendedControls(
                     engine_process=p,
                     toggle_keysym_name=extended_keyboard_control_toggle_keysym_name,
                 ).run()

+ 165 - 0
tooncher/controls.py

@@ -0,0 +1,165 @@
+import copy
+import time
+try:
+    import Xlib.display
+    from Xlib import X, XK
+except ImportError:
+    Xlib = False
+
+if Xlib:
+    EXTENDED_KEYBOARD_CONTROLS_DEFAULT_MAPPING = {
+        XK.XK_w: XK.XK_Up,
+        XK.XK_a: XK.XK_Left,
+        XK.XK_s: XK.XK_Down,
+        XK.XK_d: XK.XK_Right,
+    }
+
+
+def x_find_window(parent_window, filter_callback):
+    matching = []
+    for child_window in parent_window.query_tree().children:
+        if filter_callback(child_window):
+            matching.append(child_window)
+        matching += x_find_window(child_window, filter_callback)
+    return matching
+
+
+def x_find_window_by_pid(display, pid):
+    pid_prop = display.intern_atom('_NET_WM_PID')
+
+    def filter_callback(window):
+        prop = window.get_full_property(pid_prop, X.AnyPropertyType)
+        return prop and prop.value.tolist() == [pid]
+    return x_find_window(display.screen().root, filter_callback)
+
+
+class ExtendedControls:
+
+    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
+
+    def _wait_for_engine_window(self, timeout_seconds=20, search_interval_seconds=2):
+        start_epoch = time.time()
+        while self._engine_process.poll() is None and (time.time() - start_epoch) <= timeout_seconds:
+            windows = x_find_window_by_pid(
+                self._xdisplay,
+                self._engine_process.pid,
+            )
+            assert len(windows) <= 1
+            if len(windows) == 1:
+                return windows[0]
+            time.sleep(search_interval_seconds)
+        return None
+
+    def run(self):
+        self._engine_window = self._wait_for_engine_window()
+        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():
+            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():
+            self._ungrab_key(
+                self._xdisplay.keysym_to_keycode(keysym),
+            )
+        self._enabled = False
+        print("INFO Disabled Extended Controls")
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    def toggle(self):
+        if self.enabled:
+            self.disable()
+        else:
+            self.enable()
+
+    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)