|
@@ -3,6 +3,7 @@ import os
|
|
|
import select
|
|
|
import time
|
|
|
from tooncher.actions import *
|
|
|
+
|
|
|
try:
|
|
|
import psutil
|
|
|
except ImportError:
|
|
@@ -13,29 +14,43 @@ try:
|
|
|
except ImportError:
|
|
|
Xlib = False
|
|
|
|
|
|
-EXTENDED_CONTROLS_DEFAULT_TOGGLE_KEYSYM_NAME = 'grave'
|
|
|
-EXTENDED_CONTROLS_PID_XPROPERTY_NAME = '_TOONCHER_EXTENDED_CONTROLS_PID'
|
|
|
-TOONTOWN_WINDOW_NAME = 'Toontown Rewritten'
|
|
|
+EXTENDED_CONTROLS_DEFAULT_TOGGLE_KEYSYM_NAME = "grave"
|
|
|
+EXTENDED_CONTROLS_PID_XPROPERTY_NAME = "_TOONCHER_EXTENDED_CONTROLS_PID"
|
|
|
+TOONTOWN_WINDOW_NAME = "Toontown Rewritten"
|
|
|
|
|
|
if Xlib:
|
|
|
EXTENDED_CONTROLS_DEFAULT_KEYSYM_MAPPINGS = {
|
|
|
- XK.XK_w: RewriteKeyEventAction(keysym=XK.XK_Up, target_engine_index=0),
|
|
|
- XK.XK_a: RewriteKeyEventAction(keysym=XK.XK_Left, target_engine_index=0),
|
|
|
- XK.XK_s: RewriteKeyEventAction(keysym=XK.XK_Down, target_engine_index=0),
|
|
|
- XK.XK_d: RewriteKeyEventAction(keysym=XK.XK_Right, target_engine_index=0),
|
|
|
- XK.XK_Control_L: RewriteKeyEventAction(keysym=XK.XK_Control_L, target_engine_index=0),
|
|
|
- XK.XK_v: RewriteKeyEventAction(keysym=XK.XK_Delete, target_engine_index=0),
|
|
|
- XK.XK_i: RewriteKeyEventAction(keysym=XK.XK_Up, target_engine_index=1),
|
|
|
- XK.XK_j: RewriteKeyEventAction(keysym=XK.XK_Left, target_engine_index=1),
|
|
|
- XK.XK_k: RewriteKeyEventAction(keysym=XK.XK_Down, target_engine_index=1),
|
|
|
- XK.XK_l: RewriteKeyEventAction(keysym=XK.XK_Right, target_engine_index=1),
|
|
|
- XK.XK_slash: RewriteKeyEventAction(keysym=XK.XK_Control_L, target_engine_index=1),
|
|
|
- XK.XK_n: RewriteKeyEventAction(keysym=XK.XK_Delete, target_engine_index=1),
|
|
|
- XK.XK_space: RewriteKeyEventAction(keysym=XK.XK_Control_L, target_engine_index=TargetEngine.All),
|
|
|
- XK.XK_e: SelectGagAction(target_engine_index=0, column_index=4, factor_y=-0.047),
|
|
|
- XK.XK_o: SelectGagAction(target_engine_index=1, column_index=4, factor_y=-0.047),
|
|
|
- XK.XK_f: SelectGagAction(target_engine_index=0, column_index=5, factor_y=-0.047),
|
|
|
- XK.XK_semicolon: SelectGagAction(target_engine_index=1, column_index=5, factor_y=-0.047),
|
|
|
+ XK.XK_w: RewriteKeyEventAction(keysym=XK.XK_Up, target_engine_index=0),
|
|
|
+ XK.XK_a: RewriteKeyEventAction(keysym=XK.XK_Left, target_engine_index=0),
|
|
|
+ XK.XK_s: RewriteKeyEventAction(keysym=XK.XK_Down, target_engine_index=0),
|
|
|
+ XK.XK_d: RewriteKeyEventAction(keysym=XK.XK_Right, target_engine_index=0),
|
|
|
+ XK.XK_Control_L: RewriteKeyEventAction(
|
|
|
+ keysym=XK.XK_Control_L, target_engine_index=0
|
|
|
+ ),
|
|
|
+ XK.XK_v: RewriteKeyEventAction(keysym=XK.XK_Delete, target_engine_index=0),
|
|
|
+ XK.XK_i: RewriteKeyEventAction(keysym=XK.XK_Up, target_engine_index=1),
|
|
|
+ XK.XK_j: RewriteKeyEventAction(keysym=XK.XK_Left, target_engine_index=1),
|
|
|
+ XK.XK_k: RewriteKeyEventAction(keysym=XK.XK_Down, target_engine_index=1),
|
|
|
+ XK.XK_l: RewriteKeyEventAction(keysym=XK.XK_Right, target_engine_index=1),
|
|
|
+ XK.XK_slash: RewriteKeyEventAction(
|
|
|
+ keysym=XK.XK_Control_L, target_engine_index=1
|
|
|
+ ),
|
|
|
+ XK.XK_n: RewriteKeyEventAction(keysym=XK.XK_Delete, target_engine_index=1),
|
|
|
+ XK.XK_space: RewriteKeyEventAction(
|
|
|
+ keysym=XK.XK_Control_L, target_engine_index=TargetEngine.All
|
|
|
+ ),
|
|
|
+ XK.XK_e: SelectGagAction(
|
|
|
+ target_engine_index=0, column_index=4, factor_y=-0.047
|
|
|
+ ),
|
|
|
+ XK.XK_o: SelectGagAction(
|
|
|
+ target_engine_index=1, column_index=4, factor_y=-0.047
|
|
|
+ ),
|
|
|
+ XK.XK_f: SelectGagAction(
|
|
|
+ target_engine_index=0, column_index=5, factor_y=-0.047
|
|
|
+ ),
|
|
|
+ XK.XK_semicolon: SelectGagAction(
|
|
|
+ target_engine_index=1, column_index=5, factor_y=-0.047
|
|
|
+ ),
|
|
|
}
|
|
|
|
|
|
|
|
@@ -49,11 +64,12 @@ def x_find_window(parent_window, filter_callback):
|
|
|
|
|
|
|
|
|
def x_find_window_by_pid(display, pid):
|
|
|
- pid_prop = display.intern_atom('_NET_WM_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)
|
|
|
|
|
|
|
|
@@ -71,40 +87,54 @@ def x_wait_for_event(xdisplay, timeout_seconds):
|
|
|
|
|
|
|
|
|
class ExtendedControls:
|
|
|
-
|
|
|
- def __init__(self, primary_engine_pid, primary_engine_window_name=None,
|
|
|
- toggle_keysym_name=EXTENDED_CONTROLS_DEFAULT_TOGGLE_KEYSYM_NAME):
|
|
|
+ def __init__(
|
|
|
+ self,
|
|
|
+ primary_engine_pid,
|
|
|
+ primary_engine_window_name=None,
|
|
|
+ toggle_keysym_name=EXTENDED_CONTROLS_DEFAULT_TOGGLE_KEYSYM_NAME,
|
|
|
+ ):
|
|
|
if not psutil:
|
|
|
- raise Exception('\n'.join([
|
|
|
- 'Extended keyboard controls require the python lib psutil to be installed.',
|
|
|
- 'Depending on your system run',
|
|
|
- '\t$ sudo apt-get install python3-psutil',
|
|
|
- 'or',
|
|
|
- '\t$ pip3 install --user psutil',
|
|
|
- ]))
|
|
|
+ raise Exception(
|
|
|
+ "\n".join(
|
|
|
+ [
|
|
|
+ "Extended keyboard controls require the python lib psutil to be installed.",
|
|
|
+ "Depending on your system run",
|
|
|
+ "\t$ sudo apt-get install python3-psutil",
|
|
|
+ "or",
|
|
|
+ "\t$ pip3 install --user psutil",
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ )
|
|
|
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',
|
|
|
- ]))
|
|
|
+ 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._primary_engine_pid = primary_engine_pid
|
|
|
self._primary_engine_window_name = primary_engine_window_name
|
|
|
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))
|
|
|
+ raise Exception(
|
|
|
+ "Extended keyboard controls toggle:"
|
|
|
+ + " Unknown keysym name '{}'".format(toggle_keysym_name)
|
|
|
+ )
|
|
|
self._keysym_mappings = copy.deepcopy(
|
|
|
EXTENDED_CONTROLS_DEFAULT_KEYSYM_MAPPINGS,
|
|
|
)
|
|
|
if self._toggle_keysym in self._keysym_mappings:
|
|
|
- print("INFO Extended Controls:"
|
|
|
- + " Ignoring mapping for toggle key '{}'".format(toggle_keysym_name))
|
|
|
- self._keysym_mappings[self._toggle_keysym] \
|
|
|
- = ToggleExtendedControlsAction()
|
|
|
+ print(
|
|
|
+ "INFO Extended Controls:"
|
|
|
+ + " Ignoring mapping for toggle key '{}'".format(toggle_keysym_name)
|
|
|
+ )
|
|
|
+ self._keysym_mappings[self._toggle_keysym] = ToggleExtendedControlsAction()
|
|
|
self._default_action = ForwardKeyEventAction()
|
|
|
self._primary_engine_window = None
|
|
|
self._engine_windows_by_target_index = None
|
|
@@ -126,10 +156,7 @@ class ExtendedControls:
|
|
|
def _wait_for_engine_window(self, timeout_seconds=20, search_interval_seconds=2):
|
|
|
start_epoch = time.time()
|
|
|
while self.engine_running and (time.time() - start_epoch) <= timeout_seconds:
|
|
|
- windows = x_find_window_by_pid(
|
|
|
- self._xdisplay,
|
|
|
- self._primary_engine_pid,
|
|
|
- )
|
|
|
+ windows = x_find_window_by_pid(self._xdisplay, self._primary_engine_pid,)
|
|
|
assert len(windows) <= 1
|
|
|
if len(windows) == 1:
|
|
|
return windows[0]
|
|
@@ -139,10 +166,8 @@ class ExtendedControls:
|
|
|
def run(self):
|
|
|
self._primary_engine_window = self._wait_for_engine_window()
|
|
|
if not self._primary_engine_window:
|
|
|
- raise Exception('Could not find the game\'s window.')
|
|
|
- self._grab_key(
|
|
|
- self._xdisplay.keysym_to_keycode(self._toggle_keysym),
|
|
|
- )
|
|
|
+ raise Exception("Could not find the game's window.")
|
|
|
+ self._grab_key(self._xdisplay.keysym_to_keycode(self._toggle_keysym),)
|
|
|
self._primary_engine_window.change_property(
|
|
|
self.xdisplay.intern_atom(EXTENDED_CONTROLS_PID_XPROPERTY_NAME),
|
|
|
Xatom.CARDINAL,
|
|
@@ -151,16 +176,18 @@ class ExtendedControls:
|
|
|
mode=X.PropModeReplace,
|
|
|
)
|
|
|
if self._primary_engine_window_name:
|
|
|
- self._primary_engine_window.set_wm_name(
|
|
|
- self._primary_engine_window_name,
|
|
|
+ self._primary_engine_window.set_wm_name(self._primary_engine_window_name,)
|
|
|
+ print(
|
|
|
+ "INFO Changed engine's window name to {!r}".format(
|
|
|
+ self._primary_engine_window_name,
|
|
|
+ )
|
|
|
)
|
|
|
- print("INFO Changed engine's window name to {!r}".format(
|
|
|
- self._primary_engine_window_name,
|
|
|
- ))
|
|
|
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))
|
|
|
+ print(
|
|
|
+ "INFO Extended Controls are currently disabled."
|
|
|
+ + " Press key '{}' to enable.".format(keysym_name)
|
|
|
+ )
|
|
|
while self.engine_running:
|
|
|
while self.xdisplay.pending_events():
|
|
|
self._handle_xevent(self._xdisplay.next_event())
|
|
@@ -170,16 +197,14 @@ class ExtendedControls:
|
|
|
x_wait_for_event(self.xdisplay, timeout_seconds=0.05)
|
|
|
|
|
|
def _handle_xevent(self, xevent):
|
|
|
- if isinstance(xevent, Xlib.protocol.event.KeyPress) \
|
|
|
- or isinstance(xevent, Xlib.protocol.event.KeyRelease):
|
|
|
+ if isinstance(xevent, Xlib.protocol.event.KeyPress) or isinstance(
|
|
|
+ xevent, Xlib.protocol.event.KeyRelease
|
|
|
+ ):
|
|
|
self._handle_xkeyevent(xevent)
|
|
|
|
|
|
def _handle_xkeyevent(self, xkeyevent):
|
|
|
self._update_active_key_registry(xkeyevent)
|
|
|
- keysym_in = self._xdisplay.keycode_to_keysym(
|
|
|
- xkeyevent.detail,
|
|
|
- index=0,
|
|
|
- )
|
|
|
+ keysym_in = self._xdisplay.keycode_to_keysym(xkeyevent.detail, index=0,)
|
|
|
if keysym_in in self._keysym_mappings:
|
|
|
action = self._keysym_mappings[keysym_in]
|
|
|
else:
|
|
@@ -189,9 +214,7 @@ class ExtendedControls:
|
|
|
def enable(self):
|
|
|
for keysym in self._keysym_mappings.keys():
|
|
|
if keysym != self._toggle_keysym:
|
|
|
- self._grab_key(
|
|
|
- self._xdisplay.keysym_to_keycode(keysym),
|
|
|
- )
|
|
|
+ self._grab_key(self._xdisplay.keysym_to_keycode(keysym),)
|
|
|
self._enabled = True
|
|
|
|
|
|
self._engine_windows_by_target_index = None
|
|
@@ -200,9 +223,7 @@ class ExtendedControls:
|
|
|
def disable(self):
|
|
|
for keysym in self._keysym_mappings.keys():
|
|
|
if keysym != self._toggle_keysym:
|
|
|
- self._ungrab_key(
|
|
|
- self._xdisplay.keysym_to_keycode(keysym),
|
|
|
- )
|
|
|
+ self._ungrab_key(self._xdisplay.keysym_to_keycode(keysym),)
|
|
|
self._enabled = False
|
|
|
print("INFO Disabled Extended Controls")
|
|
|
|
|
@@ -238,7 +259,7 @@ class ExtendedControls:
|
|
|
return x_find_window(
|
|
|
self.xdisplay.screen().root,
|
|
|
lambda w: w.get_wm_name() == TOONTOWN_WINDOW_NAME
|
|
|
- or w.get_full_property(controls_xprop, X.AnyPropertyType),
|
|
|
+ or w.get_full_property(controls_xprop, X.AnyPropertyType),
|
|
|
)
|
|
|
|
|
|
@property
|
|
@@ -246,10 +267,11 @@ class ExtendedControls:
|
|
|
if not self._engine_windows_by_target_index:
|
|
|
win_by_index = {}
|
|
|
for target_index, win in enumerate(self.find_engine_windows()):
|
|
|
- print('INFO Engine window {} has no target index, assuming {}'.format(
|
|
|
- win.id,
|
|
|
- target_index,
|
|
|
- ))
|
|
|
+ print(
|
|
|
+ "INFO Engine window {} has no target index, assuming {}".format(
|
|
|
+ win.id, target_index,
|
|
|
+ )
|
|
|
+ )
|
|
|
if not target_index in win_by_index:
|
|
|
win_by_index[target_index] = []
|
|
|
win_by_index[target_index].append(win)
|
|
@@ -286,18 +308,20 @@ class ExtendedControls:
|
|
|
bit_index = keycode & ((1 << 3) - 1)
|
|
|
if not keymap[byte_index] & (1 << bit_index):
|
|
|
print("DEBUG missed release event of key {}".format(keycode))
|
|
|
- missed_releases.append(Xlib.protocol.event.KeyRelease(
|
|
|
- window=press_event.window,
|
|
|
- detail=press_event.detail,
|
|
|
- state=press_event.state,
|
|
|
- root_x=press_event.root_x,
|
|
|
- root_y=press_event.root_y,
|
|
|
- event_x=press_event.event_x,
|
|
|
- event_y=press_event.event_y,
|
|
|
- child=press_event.child,
|
|
|
- root=press_event.root,
|
|
|
- time=X.CurrentTime,
|
|
|
- same_screen=press_event.same_screen,
|
|
|
- ))
|
|
|
+ missed_releases.append(
|
|
|
+ Xlib.protocol.event.KeyRelease(
|
|
|
+ window=press_event.window,
|
|
|
+ detail=press_event.detail,
|
|
|
+ state=press_event.state,
|
|
|
+ root_x=press_event.root_x,
|
|
|
+ root_y=press_event.root_y,
|
|
|
+ event_x=press_event.event_x,
|
|
|
+ event_y=press_event.event_y,
|
|
|
+ child=press_event.child,
|
|
|
+ root=press_event.root,
|
|
|
+ time=X.CurrentTime,
|
|
|
+ same_screen=press_event.same_screen,
|
|
|
+ )
|
|
|
+ )
|
|
|
for release_event in missed_releases:
|
|
|
self._handle_xkeyevent(release_event)
|