Browse Source

Action is being defined, added Xlib for mouse

It appears the mouse module will work for platforms such as Windows, but
not Linux... and pyautogui was simple enough, I just copied it without
their extra mess of moving the mouse (which I never wanted it to do)

> python3-xlib is added for X11 mouse support, now done by us
apollo 5 days ago
parent
commit
d5c18a66ba
2 changed files with 114 additions and 22 deletions
  1. 3 2
      requirements.txt
  2. 111 20
      tyrell.py

+ 3 - 2
requirements.txt

@@ -1,7 +1,6 @@
 click==8.1.8
 click==8.1.8
 colorama==0.4.6
 colorama==0.4.6
 keyboard==0.13.5
 keyboard==0.13.5
-mouse==0.7.1
 MouseInfo==0.1.3
 MouseInfo==0.1.3
 pillow==10.4.0
 pillow==10.4.0
 PyAutoGUI==0.9.54
 PyAutoGUI==0.9.54
@@ -9,7 +8,9 @@ PyGetWindow==0.0.9
 PyMsgBox==1.0.9
 PyMsgBox==1.0.9
 pyperclip==1.9.0
 pyperclip==1.9.0
 PyRect==0.2.0
 PyRect==0.2.0
-pyscreeze==1.0.1
+PyScreeze==1.0.1
+python-xlib==0.33
 python3-xlib==0.15
 python3-xlib==0.15
 pytweening==1.2.0
 pytweening==1.2.0
+six==1.17.0
 toml==0.10.2
 toml==0.10.2

+ 111 - 20
tyrell.py

@@ -7,7 +7,7 @@ from os.path import exists as _exists, join as _join, isdir as _isdir
 
 
 from time import sleep as _sleep
 from time import sleep as _sleep
 
 
-from typing import Dict as _Dict, Tuple as _Tuple, List as _List
+from typing import Dict as _Dict, Tuple as _Tuple, List as _List, Optional as _Option, Any as _Any
 
 
 try:
 try:
     from click import echo as _echo, style as _style
     from click import echo as _echo, style as _style
@@ -15,16 +15,6 @@ except ImportError:
     print("ERROR Please activate a virtual environment and install requirements.txt")
     print("ERROR Please activate a virtual environment and install requirements.txt")
     exit()
     exit()
 
 
-from pyautogui import mouseDown as _mouse_down, mouseUp as _mouse_up
-from keyboard import press as _key_down, release as _key_up, KeyboardEvent as _KeyboardEvent, add_hotkey as _add_hotkey, hook as _key_hook, write as _key_write
-# Not entirely sure why this doesn't work
-#from mouse import press as mouse_down, release as mouse_up, click as mouse_press, move as mouse_move
-
-from toml import load as _toml_load
-
-from asyncio import run as _run, sleep as _asleep
-from asyncio.tasks import gather as _task_group
-
 def _print_err(msg: str):
 def _print_err(msg: str):
     _echo(_style("ERROR", fg="bright_red") + " " + msg)
     _echo(_style("ERROR", fg="bright_red") + " " + msg)
 
 
@@ -37,16 +27,10 @@ def _print_ok(msg: str):
 def _print_info(msg: str):
 def _print_info(msg: str):
     print("      " + msg)
     print("      " + msg)
 
 
-def _keyboard_output(msg: str, delay: int=50, hold: int=20):
-    _key_write(msg, delay=hold)
-    _sleep(delay/1000)
+from pyautogui import LEFT as _LEFT, RIGHT as _RIGHT, MIDDLE as _MIDDLE
+from keyboard import press as _key_down, release as _key_up, KeyboardEvent as _KeyboardEvent, add_hotkey as _add_hotkey, hook as _key_hook, write as _key_write
 
 
-def _mouse_output(button: str, delay: int=50, hold: int=20):
-    _mouse_down(button=button)
-    _sleep(hold / 1000)
-    _mouse_up(button=button)
-    _sleep(hold / 1000)
-    _sleep(delay / 1000)
+_BUTTON_MAP: _Dict[str | int, int] = {_LEFT: 1, _MIDDLE:3, _RIGHT: 3, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7}
 
 
 platform: str = ""
 platform: str = ""
 os_name = _OS_NAME.lower()
 os_name = _OS_NAME.lower()
@@ -61,6 +45,46 @@ else:
     _print_info(f"Platform: {_OS_NAME}")
     _print_info(f"Platform: {_OS_NAME}")
     exit()
     exit()
 
 
+if platform == 'LINUX':
+    # It appears those under LINUX, either don't work or work but play with mouse position
+    from Xlib.display import Display as _Display
+    _display = _Display(_env['DISPLAY'])
+    from Xlib.X import ButtonPress as _ButtonPress, ButtonRelease as _ButtonRelease
+    from Xlib.ext.xtest import fake_input as _fake_input
+    def _mouse_down(button: str | int=_LEFT):
+        assert button in _BUTTON_MAP.keys(), "button argument not in ('left', 'middle', 'right', 1, 2, 3, 4, 5, 6, 7)"
+        btn = _BUTTON_MAP[button]
+        _fake_input(_display, _ButtonPress, btn)
+        _display.sync()
+    def _mouse_up(button: str | int=_LEFT):
+        assert button in _BUTTON_MAP.keys(), "button argument not in ('left', 'middle', 'right', 1, 2, 3, 4, 5, 6, 7)"
+        btn = _BUTTON_MAP[button]
+        _fake_input(_display, _ButtonRelease, btn)
+        _display.sync()
+    def _click(button: str|int=_LEFT):
+        assert button in _BUTTON_MAP.keys(), "button argument not in ('left', 'middle', 'right', 1, 2, 3, 4, 5, 6, 7)"
+        _mouse_down(button)
+        _mouse_up(button)
+else:
+    # We can simply use the mouse module under WINDOWS and MACOS
+    from mouse import press as _mouse_down, release as _mouse_up, click as _click
+
+from toml import load as _toml_load
+
+from asyncio import run as _run, sleep as _asleep
+from asyncio.tasks import gather as _task_group
+
+def _keyboard_output(msg: str, delay: int=50, hold: int=20):
+    _key_write(msg, delay=hold)
+    _sleep(delay/1000)
+
+def _mouse_output(button: str, delay: int=50, hold: int=20):
+    _mouse_down(button=button)
+    _sleep(hold / 1000)
+    _mouse_up(button=button)
+    _sleep(hold / 1000)
+    _sleep(delay / 1000)
+
 def _profile_pre_check(profile_name: str):
 def _profile_pre_check(profile_name: str):
     if profile_name.endswith(".toml"):
     if profile_name.endswith(".toml"):
         profile_name = profile_name.removesuffix(".toml")
         profile_name = profile_name.removesuffix(".toml")
@@ -133,6 +157,10 @@ def _write_example(profile_name: str):
             '',
             '',
             "# When 'h' is pressed, write 'Hello World!'",
             "# When 'h' is pressed, write 'Hello World!'",
             '',
             '',
+            '# Do we want this key-bind active on start-up',
+            "# This is a simple example, it won't use this",
+            '#startup = true',
+            '',
             '# Key to trigger on',
             '# Key to trigger on',
             "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
             "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
             "key = 'h'",
             "key = 'h'",
@@ -203,6 +231,10 @@ def _write_example_mirror(profile_name: str):
             "# Press alt when 'w' is down, and release when 'w' is up",
             "# Press alt when 'w' is down, and release when 'w' is up",
             "# Use 'z' to toggle on/off",
             "# Use 'z' to toggle on/off",
             '',
             '',
+            '# Do we want this key-bind active on start-up',
+            "# This is a simple example, it won't use this",
+            '#startup = true',
+            '',
             '# Key to toggle',
             '# Key to toggle',
             "key = 'z'",
             "key = 'z'",
             '',
             '',
@@ -226,6 +258,63 @@ def _write_example_mirror(profile_name: str):
     if "SUDO_USER" in _env:
     if "SUDO_USER" in _env:
         _chown(_join(profile_name, "keys", "example_mirror.toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID']))
         _chown(_join(profile_name, "keys", "example_mirror.toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID']))
 
 
+class Action:
+    # Toggle to determine if this action is 'on' or 'off'
+    state: bool
+    # Key to trigger on (toggle on mirror)
+    key: str
+    # Kind of action to perform ('click', 'click down', 'key', 'key down', 'mirror')
+    kind: str
+    # Optional, keys to send
+    write: _Option[str]
+    # Optional, Repeat X number of ticks
+    duration: _Option[int]
+    # Optional, mouse button to send
+    button: _Option[str]
+    # Optional, Key to mirror
+    mirror: _Option[str]
+    # Optional, Override, Delay between each key (in milliseconds)
+    delay: _Option[int]
+    # Optional, Override, Hold delay for each key (in milliseconds)
+    hold: _Option[int]
+    def __init__(self, data: _Dict[str, _Option[_Any]]):
+        # Validate data!
+        assert 'key' in data, "Action missing 'key'"
+        assert 'kind' in data, "Action missing 'kind'"
+        assert data['kind'] in ('click', 'click down', 'key', 'key down', 'mirror'), f"Action 'kind' is unsupported, got '{data['kind']}'"
+        assert 'write' in data or 'button' in data, "Action missing 'write' or 'button' (one needed)"
+        # Assign class data
+        if 'startup' in data:
+            self.state = True
+        else:
+            self.state = False
+        self.key = str(data['key'])
+        self.kind = str(data['kind'])
+        if 'write' in data:
+            self.write = str(data['write'])
+        else:
+            self.write = None
+        if 'button' in data:
+            self.button = str(data['button'])
+        else:
+            self.button = None
+        if 'mirror' in data:
+            self.mirror = str(data['mirror'])
+        else:
+            self.mirror = None
+        if 'duration' in data:
+            self.duration = int(data['duration'])
+        else:
+            self.duration = None
+        if 'delay' in data:
+            self.delay = int(data['delay'])
+        else:
+            self.delay = None
+        if 'hold' in data:
+            self.hold = int(data['hold'])
+        else:
+            self.hold = None
+
 class Profile:
 class Profile:
     name: str
     name: str
     placeholders: _Dict[str, bool]
     placeholders: _Dict[str, bool]
@@ -289,6 +378,8 @@ class Profile:
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
     if not _exists("_example"):
     if not _exists("_example"):
+        _print_warn("Missing '_example' profile, creating...")
         p = Profile("_example")
         p = Profile("_example")
+        _print_ok("")
         exit()
         exit()
     _print_ok("All okay")
     _print_ok("All okay")