from sys import exit, argv from os import name as OS_NAME from os import listdir from os.path import exists, join, isdir from time import sleep from typing import Dict try: from click import echo, style except ImportError: print("ERROR Please activate a virtual environment and install requirements.txt") exit() from pyautogui import mouseDown as mouse_down, mouseUp as mouse_up from keyboard import press as key_down, release as key_up, KeyboardEvent, add_hotkey, 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, sleep as asleep from asyncio.tasks import gather as task_group def print_err(msg): echo(style("ERROR", fg="bright_red") + " " + msg) def print_warn(msg): echo(style("WARN ", fg="bright_yellow") + " " + msg) def print_ok(msg): echo(style(" OK ", fg="bright_green") + " " + msg) def print_info(msg): print(" " + msg) def keyboard_output(msg: str, delay: int=50, hold: int=20): key_write(msg, delay=hold) sleep(delay/1000) # for k in msg: # if k.isupper(): # k = k.lower() # key_down('shift') # key_down(k) # sleep(hold / 1000) # key_up(k) # key_up('shift') # sleep(hold / 1000) # elif k in ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')']: # m = {'!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0'} # key_down('shift') # key_down(m[k]) # sleep(hold / 1000) # key_up(m[k]) # key_up('shift') # sleep(hold / 1000) # elif k in ['\r', '\n']: # m = {'\r': 'enter', '\n': 'enter'} # key_down(m[k]) # sleep(hold / 1000) # key_up(m[k]) # sleep(hold / 1000) # else: # key_down(k) # sleep(hold / 1000) # key_up(k) # sleep(hold / 1000) # 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) 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() class Action(): name: str kind: str extra: Dict toggled: bool delay: int max_delay: int def __init__(self, name, kind, extra=None, toggled=False, delay=0): self.name = name self.kind = kind if extra is not None: self.extra = extra else: self.extra = {} self.toggled = toggled self.delay = delay self.max_delay = delay def toggle(self) -> bool: self.toggled = not self.toggled return self.toggled def tick(self) -> bool: if not self.toggled: if self.delay != self.max_delay: self.delay = self.max_delay return False if self.max_delay != 0: self.delay -= 1 if self.delay <= 0: self.delay = self.max_delay return True return False def do(self, delay: int=50, hold: int=20): if not self.toggled: return d = delay h = hold if self.extra is not None: if "delay" in self.extra: d = self.extra["delay"] if "hold" in self.extra: h = self.extra["hold"] if self.kind == "click": mouse_output(self.extra["button"], d, h) elif self.kind == "click down": mouse_down(self.extra["button"]) elif self.kind == "key": keyboard_output(self.extra["write"], d, h) elif self.kind == "key down": key_down(self.extra["write"]) class Tyrell(): keybinds: Dict[str, Action] mirrors: Dict[str, str] # mirror key -> key in keybinds toggled: bool delay: int hold: int def __init__(self, profile_name: str): self.profile = { "activator": "`", "helper": "?", "delay": 50, "hold": 20, "tick": 50, "placeholder_name": False, } self.name = profile_name if profile_name.endswith(".toml"): self.name = profile_name.removesuffix(".toml") if not exists(self.name) or not isdir(self.name): print_err(f"Invalid profile '{self.name}'") print_info(f"This should be a directory") exit() profile_config = join(self.name, self.name+".toml") if exists(profile_config): with open(profile_config, "r") as f: try: t = toml_load(f) except Exception as err: print_err(f"Invalid profile '{self.name}'") print_info(f"Invalid '{self.name+'.toml'}' config") print(err) exit() self.profile = t if "activator" not in self.profile: self.profile["activator"] = "`" if "helper" not in self.profile: self.profile["helper"] = "?" if "hold" not in self.profile: self.profile["hold"] = 20 if "delay" not in self.profile: self.profile["delay"] = 50 if "tick" not in self.profile: self.profile["tick"] = 50 if "placeholder_name" not in self.profile: self.profile["placeholder_name"] = False else: print_err(f"Invalid profile '{self.name}'") print_info(f"Missing '{self.name+'.toml'}' config") exit() self.delay = self.profile["delay"] self.hold = self.profile["hold"] self.keybinds = {} self.mirrors = {} self.toggled = False if not exists(join(self.name, 'keys')): print_err(f"Invalid profile '{self.name}'") print_info("Missing 'keys' directory (for keybinds)") exit() else: for ent in listdir(join(self.name, "keys")): if ent.startswith(".") or isdir(ent) or not ent.endswith(".toml"): continue with open(join(self.name, "keys", ent), "r") as f: t = toml_load(f) if t["keybind"] == "?" or t["keybind"] == "`": continue if "duration" in t: self.add_action(t["keybind"], Action(ent.removesuffix(".toml"), t["kind"], extra=t, delay=t["duration"])) else: self.add_action(t["keybind"], Action(ent.removesuffix(".toml"), t["kind"], extra=t)) def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool: self.toggled = not self.toggled if all: for key in self.keybinds: act = self.keybinds[key] act.toggle() elif all_tickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay != 0: act.toggle() elif all_notickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0: act.toggle() return self.toggled def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False): self.toggled = False if all: for key in self.keybinds: act = self.keybinds[key] act.toggled = False elif all_tickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay != 0: act.toggled = False elif all_notickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0: act.toggled = False def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False): self.toggled = True if all: for key in self.keybinds: act = self.keybinds[key] act.toggled = True elif all_tickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay != 0: act.toggled = True elif all_notickers: for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0: act.toggled = True def add_action(self, bind: str, act: Action): act.toggled = False if "placeholder_name" in self.profile: if self.profile["placeholder_name"] and "write" in act.extra: act.extra["write"] = act.extra["write"].replace("{name}", self.name) if act.kind == "mirror" and "mirror" in act.extra: self.mirrors[act.extra["mirror"]] = bind self.keybinds[bind] = act def remove_action(self, bind: str): self.keybinds[bind] = None def is_action(self, bind: str) -> bool: return self.keybinds[bind] is not None def print_help(self): print_ok(f"{int(1000 / self.profile['tick'])} ticks per second ({self.profile['tick']} ms per tick)") print_ok(f"Name placeholder: {self.profile['placeholder_name']}") print_warn(f"{self.profile['activator']} -> Activate/Deactivate") print_warn(f"{self.profile['helper']} -> Displays Help") for key in self.keybinds: act = self.keybinds[key] if act.max_delay == 0 and act.toggled: print_info(f"{key} -> {act.name}") else: print_info(f"{key} -> {act.name} = {act.toggled} ({act.max_delay} ticks)") print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop") def tick(self): for key in self.keybinds: act = self.keybinds[key] if act.tick(): act.do(self.delay, self.hold) # def callback(self, event: KeyboardEvent): # key_name = event.name # #if key_name in self.mirrors: # # # key mirrors currently lag # # act = self.keybinds[self.mirrors[key_name]] # # if act.toggled: # # if "write" in act.extra: # # if event.event_type == "down": # # key_down(act.extra["write"]) # # else: # # key_up(act.extra["write"]) # # elif "button" in act.extra: # # if event.event_type == "down": # # mouse_down(button=act.extra["button"]) # # else: # # mouse_up(button=act.extra["button"]) # #if event.event_type == "up": # if key_name == self.profile["activator"]: # if self.toggle(): # print_ok("ON") # else: # print_ok("OFF") # elif self.toggled: # #print(f"Name: {event.name}") # #print(f"Code: {event.scan_code}") # #print(f"Modifiers: {event.modifiers}") # if self.profile["helper"] == "?" and "shift" in event.modifiers and event.name == "/" or key_name == self.profile["helper"]: # self.print_help() # elif key_name in self.keybinds: # act = self.keybinds[key_name] # if act.max_delay == 0 and act.toggled: # print_info(f"{key_name} -> {act.kind}") # act.do(self.delay, self.hold) # else: # print_info(f"{key_name} -> {act.kind} = {act.toggle()}") # self.toggle() # print_ok("OFF") def callback(self, bind, state=None, mirror_host=None): if bind == "__toggle__": if self.toggle(): print_info(style("ON", fg="bright_green")) else: print_info(style("OFF", fg="bright_red")) return if state == "up" or state == "down": act = self.keybinds[mirror_host] if act.toggled: if act.extra["write"] != None or len(act.extra["write"]) != 0: if state == "up": print(f"'{bind}' -> {act.name} UP") key_up(act.extra["write"]) elif state == "down": print(f"'{bind}' -> {act.name} DOWN") key_down(act.extra["write"]) elif act.extra["button"] != None or len(act.extra["button"]) != 0: if state == "up": print(f"'{bind}' -> {act.name} UP") mouse_up(button=act.extra["button"]) elif state == "down": print(f"'{bind}' -> {act.name} DOWN") mouse_down(button=act.extra["button"]) return if not self.toggled: return if bind == "__help__": self.print_help() self.disable() print_info(style("OFF", fg="bright_red")) return act = self.keybinds[bind] if act.toggled: print(f"'{bind}' -> {act.name}") act.do(self.delay, self.hold) elif act.max_delay != 0 or act.kind == "mirror": if act.toggle(): print(f"'{bind}' -> {act.name} " + style("ON", fg="bright_green")) else: print(f"'{bind}' -> {act.name} " + style("OFF", fg="bright_red")) self.disable() print_info(style("OFF", fg="bright_red")) async def background(self): while True: self.tick() await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft) async def mainloop(self): #keyboard_hook(self.callback) add_hotkey(41, self.callback, args=["__toggle__"]) add_hotkey("shift+?", self.callback, args=["__help__"]) for bind in self.keybinds: act = self.keybinds[bind] if act.kind == "key" or act.kind == "key down": print(f"hotkey -=> {bind}") add_hotkey(bind, self.callback, args=[bind]) elif act.kind == "mirror": print(f"hotkey -=> {bind} (mirror {act.extra['mirror']})") add_hotkey(bind, self.callback, args=[bind]) add_hotkey(bind, self.callback, args=[act.extra["mirror"], "down", bind]) add_hotkey(bind, self.callback, args=[act.extra["mirror"], "up", bind], trigger_on_release=True) await task_group( self.background() ) if __name__ == "__main__": if len(argv) == 1: print_err("Missing profile filename") print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'") print() print_info("Profiles allow you to add placeholders,") print_info("And defines in a \"global\" sense key delay and hold delay.") print() exit() ty = Tyrell(",".join(argv[1:])) if len(ty.keybinds) == 0: print_err("Missing keybinds") print_info("(Might want some keybinds, place them in a 'keys' directory)") print_info(f"( Need an example? Look at '{join('_example', 'keys', 'example.toml')}')") exit() ty.enable(all_notickers=True) ty.disable() ty.print_help() if ty.name == "_example": print_warn("This is the " + style("example", fg="bright_yellow") + ", please define you're own profile") print_info("Please DO NOT EDIT this example profile") try: run(ty.mainloop()) except KeyboardInterrupt: exit()