from sys import argv as _argv from os import name as _OS_NAME, environ as _env from os import listdir as _listdir, mkdir as _mkdir, chown as _chown from os.path import exists as _exists, join as _join, isdir as _isdir from time import sleep as _sleep from typing import Dict as _Dict, Tuple as _Tuple, List as _List, Optional as _Option, Any as _Any try: from click import echo as _echo, style as _style except ImportError: print("ERROR Please activate a virtual environment and install requirements.txt") exit() def _print_err(msg: str): _echo(_style("ERROR", fg="bright_red") + " " + msg) def _print_warn(msg: str): _echo(_style("WARN ", fg="bright_yellow") + " " + msg) def _print_ok(msg: str): _echo(_style(" OK ", fg="bright_green") + " " + msg) def _print_info(msg: str): print(" " + msg) 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 _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 = "" os_name = _OS_NAME.lower() if "linux" in os_name or "posix" in os_name: platform = "LINUX" elif "windows" in os_name or "nt" in os_name: platform = "WINDOWS" elif "darwin" in os_name: platform = "MACOS" else: _print_err("Platform 'Linux', 'Windows' or 'MacOS' expected") _print_info(f"Platform: {_OS_NAME}") 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): if profile_name.endswith(".toml"): profile_name = profile_name.removesuffix(".toml") if not _exists(profile_name): _mkdir(profile_name) if "SUDO_USER" in _env: _chown(profile_name, int(_env['SUDO_UID']), int(_env['SUDO_GID'])) if not _exists(_join(profile_name, "keys")): _mkdir(_join(profile_name, "keys")) if "SUDO_USER" in _env: _chown(_join(profile_name, "keys"), int(_env['SUDO_UID']), int(_env['SUDO_GID'])) def _write_profile(profile_name: str): if profile_name.endswith(".toml"): profile_name = profile_name.removesuffix(".toml") _profile_pre_check(profile_name) with open(_join(profile_name, profile_name+".toml"), "w") as f: f.writelines("\n".join([ '# Tyrell :: v0.1-dev :: More human than human', '', '# Placeholders', '', "# Enable '{name}', emitting profile name", "placeholder_name = 'true'", '', '# Tick speed', '# Value in milliseconds', "# Defaults to 20 ticks per second", "# Can't be overridden", 'tick = 50', '', '# What is the key to activate/deactivate Tyrell', "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)", "# Can't be overridden", "activator = '`'", '', '# What is the key to display help', "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)", "# Defaults to '?'", "# Can't be overridden", "helper = 'shift+/'", '', '# These can be overridden per key-bind', '', '# Delay between each keys', '# Value in milliseconds', 'delay = 50', '', '# Hold delay for each key', '# Keys are sent like below:', '# 1. key down', '# 2. hold delay', '# 3. key up', '# 4. hold delay', '# 5. delay (see above)', '# 6. Repeat for keys', '# Value in milliseconds', 'hold = 20', ])) if "SUDO_USER" in _env: _chown(_join(profile_name, profile_name+".toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID'])) def _write_example(profile_name: str): if profile_name.endswith(".toml"): profile_name = profile_name.removesuffix(".toml") _profile_pre_check(profile_name) with open(_join(profile_name, "keys", "example.toml"), "w") as f: f.writelines("\n".join([ '# Example Key-bind', '', "# 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', "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)", "key = 'h'", '', '# Kind of action to perform', '# click -> Click a mouse button', '# click down -> Click a mouse button down, but never release up', '# key -> Type the following keys, pressing each key', '# key down -> Press the following keys down, never releasing them', '# mirror -> When the mirror key is pressed down so is key or button, on release so are key or button', '# See also example_mirror.toml', "kind = 'key'", '', '# Type this', "# This is required for 'key' and 'key down' kinds (Optional for 'mirror')", "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)", "# This example will write 'Hello World!'", "write = 'shift+h, e, l, l, o, space, shift+w, o, r, l, d, shift+1'", '', '# If this should repeat X number of ticks', "# This is a simple example, it won't use this", '# Assuming tick speed is 20ms, this would occur every second', '#duration = 50', '', '# Use this mouse button', "# This is a simple example, it won't use this", "# This is required for 'click' and 'click down' kinds (Optional for 'mirror')", "# There currently is 'left' or 'right'", "#button = 'left'", '', '# Mirror key', "# This is a simple example, it won't use this", '# This is the key whose state will be mirrored', '# See also example_mirror.toml', '#', "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)", "# This example will press 'g' when 'h' is down, and release 'g' when 'h' is up", "#mirror = 'g'", '', '# Optional Overrides', '', '# Override delay between each keys', '# Value in milliseconds', 'delay = 50', '', '# Override hold delay for each key', '# Keys are sent like below:', '# 1. key down', '# 2. hold delay', '# 3. key up', '# 4. hold delay', '# 5. delay (see above)', '# 6. Repeat for keys', '# Value in milliseconds', 'hold = 20', ])) if "SUDO_USER" in _env: _chown(_join(profile_name, "keys", "example.toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID'])) def _write_example_mirror(profile_name: str): if profile_name.endswith(".toml"): profile_name = profile_name.removesuffix(".toml") _profile_pre_check(profile_name) with open(_join(profile_name, "keys", "example_mirror.toml"), "w") as f: f.writelines("\n".join([ '# Example Mirror Key-bind', '', "# Press alt when 'w' is down, and release when 'w' is up", "# 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 = 'z'", '', '# Kind of event', "kind = 'mirror'", '', '# Mirror what key', "mirror = 'w'", '', '# When mirror is down, so will this', '# When mirror is up, so will this', "write = 'alt'", '', '# Or a mouse button could be configured', "# But this example doesn't", "#button = 'right'", '', '# You could override delay and hold', '# Tyrell will turn them off for Mirror kinds automatically', ])) if "SUDO_USER" in _env: _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: name: str placeholders: _Dict[str, bool] tick: int delay: int hold: int activator: str helper: str def __init__(self, name: str): # Unify name without extension if name.endswith(".toml"): self.name = name.removesuffix(".toml") else: self.name = name # Pre-Check if not _exists(self.name): # Does not exist, new everything _write_profile(self.name) _write_example(self.name) _write_example_mirror(self.name) else: # Profile config? if not _exists(_join(self.name, self.name+".toml")): _write_profile(self.name) # Do we have keys? if not _exists(_join(self.name, "keys")): _write_example(self.name) _write_example_mirror(self.name) # Initial load self.reload() def reload(self): # Load toml config with open(_join(self.name, self.name+".toml"), "r") as f: dat = _toml_load(f) self.placeholders = { "name": False, } self.tick = 50 self.activator = "`" self.helper = "shift+/" self.delay = 50 self.hold = 20 for key in dat: val = dat[key] if key.startswith("placeholder_"): if str(val) in ('true', 'TRUE', 'True', 'T', 't', 'yes', 'YES', 'Yes', 'Y', 'y', '1'): self.placeholders[key.removeprefix("placeholder_")] = True else: self.placeholders[key.removeprefix("placeholder_")] = False elif key == "tick": self.tick = int(val) elif key == "delay": self.delay = int(val) elif key == "hold": self.hold = int(val) elif key == "activator": self.activator = str(val) elif key == "helper": self.helper = str(val) if __name__ == "__main__": if not _exists("_example"): _print_warn("Missing '_example' profile, creating...") p = Profile("_example") _print_ok("") exit() _print_ok("All okay")