|
@@ -4,8 +4,15 @@ import os
|
|
import ssl
|
|
import ssl
|
|
import subprocess
|
|
import subprocess
|
|
import sys
|
|
import sys
|
|
|
|
+import time
|
|
|
|
+import traceback
|
|
import urllib.parse
|
|
import urllib.parse
|
|
import urllib.request
|
|
import urllib.request
|
|
|
|
+try:
|
|
|
|
+ import Xlib.display
|
|
|
|
+ from Xlib import X, XK
|
|
|
|
+except ImportError:
|
|
|
|
+ Xlib = False
|
|
|
|
|
|
"""
|
|
"""
|
|
official api documentation:
|
|
official api documentation:
|
|
@@ -29,6 +36,14 @@ else:
|
|
TOONTOWN_LIBRARY_PATH = None
|
|
TOONTOWN_LIBRARY_PATH = None
|
|
TOONTOWN_ENGINE_DEFAULT_PATH = None
|
|
TOONTOWN_ENGINE_DEFAULT_PATH = None
|
|
|
|
|
|
|
|
+if Xlib:
|
|
|
|
+ EXTENDED_KEYBOARD_CONTROLS_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):
|
|
def start_engine(engine_path, gameserver, playcookie, **kwargs):
|
|
env = {
|
|
env = {
|
|
@@ -121,8 +136,99 @@ def login(username=None, password=None,
|
|
raise Exception(repr(resp_data))
|
|
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)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def x_grab_key(grab_window, keycode, modifiers=None):
|
|
|
|
+ if modifiers is None:
|
|
|
|
+ modifiers = X.AnyModifier
|
|
|
|
+ grab_window.grab_key(
|
|
|
|
+ keycode,
|
|
|
|
+ modifiers,
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ True,
|
|
|
|
+ X.GrabModeAsync,
|
|
|
|
+ X.GrabModeAsync,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def wait_for_engine_window(xdisplay, engine_process):
|
|
|
|
+ while engine_process.poll() is None:
|
|
|
|
+ windows = x_find_window_by_pid(xdisplay, engine_process.pid)
|
|
|
|
+ assert len(windows) <= 1
|
|
|
|
+ if len(windows) == 1:
|
|
|
|
+ return windows[0]
|
|
|
|
+ time.sleep(2)
|
|
|
|
+ return None
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def run_extended_keyboard_controls(engine_process):
|
|
|
|
+ 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.')
|
|
|
|
+
|
|
|
|
+ for keysym in EXTENDED_KEYBOARD_CONTROLS_MAPPING.keys():
|
|
|
|
+ x_grab_key(
|
|
|
|
+ engine_window,
|
|
|
|
+ xdisplay.keysym_to_keycode(keysym),
|
|
|
|
+ )
|
|
|
|
+ while engine_process.poll() is None:
|
|
|
|
+
|
|
|
|
+ xevent = xdisplay.next_event()
|
|
|
|
+
|
|
|
|
+ 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 in EXTENDED_KEYBOARD_CONTROLS_MAPPING:
|
|
|
|
+ keysym_out = EXTENDED_KEYBOARD_CONTROLS_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,
|
|
|
|
+ same_screen=xevent.same_screen,
|
|
|
|
+ ))
|
|
|
|
+
|
|
|
|
+
|
|
def launch(engine_path, username, password, validate_ssl_certs=True,
|
|
def launch(engine_path, username, password, validate_ssl_certs=True,
|
|
- cpu_limit_percent=None):
|
|
+ cpu_limit_percent=None, enable_extended_keyboard_controls=False):
|
|
result = login(
|
|
result = login(
|
|
username=username,
|
|
username=username,
|
|
password=password,
|
|
password=password,
|
|
@@ -146,7 +252,18 @@ def launch(engine_path, username, password, validate_ssl_certs=True,
|
|
'--limit', str(cpu_limit_percent),
|
|
'--limit', str(cpu_limit_percent),
|
|
|
|
|
|
])
|
|
])
|
|
- p.wait()
|
|
+ if enable_extended_keyboard_controls:
|
|
|
|
+ try:
|
|
|
|
+ run_extended_keyboard_controls(
|
|
|
|
+ engine_process=p,
|
|
|
|
+ )
|
|
|
|
+ except Exception as e:
|
|
|
|
+ if isinstance(e, KeyboardInterrupt):
|
|
|
|
+ raise e
|
|
|
|
+ else:
|
|
|
|
+ traceback.print_exc()
|
|
|
|
+ if p.poll() is None:
|
|
|
|
+ p.wait()
|
|
else:
|
|
else:
|
|
raise Exception(repr(result))
|
|
raise Exception(repr(result))
|
|
|
|
|