tyrell.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. from sys import exit, argv
  2. from os import name as OS_NAME
  3. from os import listdir
  4. from os.path import exists, join, isdir
  5. from time import sleep
  6. from typing import Dict
  7. try:
  8. from click import echo, style
  9. except ImportError:
  10. print("ERROR Please activate a virtual environment and install requirements.txt")
  11. exit()
  12. from pyautogui import mouseDown as mouse_down, mouseUp as mouse_up
  13. from keyboard import press as key_down, release as key_up, KeyboardEvent, add_hotkey, write as key_write
  14. # Not entirely sure why this doesn't work
  15. #from mouse import press as mouse_down, release as mouse_up, click as mouse_press, move as mouse_move
  16. from toml import load as toml_load
  17. from asyncio import run, sleep as asleep
  18. from asyncio.tasks import gather as task_group
  19. def print_err(msg):
  20. echo(style("ERROR", fg="bright_red") + " " + msg)
  21. def print_warn(msg):
  22. echo(style("WARN ", fg="bright_yellow") + " " + msg)
  23. def print_ok(msg):
  24. echo(style(" OK ", fg="bright_green") + " " + msg)
  25. def print_info(msg):
  26. print(" " + msg)
  27. def keyboard_output(msg: str, delay: int=50, hold: int=20):
  28. key_write(msg, delay=hold)
  29. sleep(delay/1000)
  30. # for k in msg:
  31. # if k.isupper():
  32. # k = k.lower()
  33. # key_down('shift')
  34. # key_down(k)
  35. # sleep(hold / 1000)
  36. # key_up(k)
  37. # key_up('shift')
  38. # sleep(hold / 1000)
  39. # elif k in ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')']:
  40. # m = {'!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0'}
  41. # key_down('shift')
  42. # key_down(m[k])
  43. # sleep(hold / 1000)
  44. # key_up(m[k])
  45. # key_up('shift')
  46. # sleep(hold / 1000)
  47. # elif k in ['\r', '\n']:
  48. # m = {'\r': 'enter', '\n': 'enter'}
  49. # key_down(m[k])
  50. # sleep(hold / 1000)
  51. # key_up(m[k])
  52. # sleep(hold / 1000)
  53. # else:
  54. # key_down(k)
  55. # sleep(hold / 1000)
  56. # key_up(k)
  57. # sleep(hold / 1000)
  58. # sleep(delay / 1000)
  59. def mouse_output(button: str, delay: int=50, hold: int=20):
  60. mouse_down(button=button)
  61. sleep(hold / 1000)
  62. mouse_up(button=button)
  63. sleep(hold / 1000)
  64. sleep(delay / 1000)
  65. platform: str = ""
  66. os_name = OS_NAME.lower()
  67. if "linux" in os_name or "posix" in os_name:
  68. platform = "LINUX"
  69. elif "windows" in os_name or "nt" in os_name:
  70. platform = "WINDOWS"
  71. elif "darwin" in os_name:
  72. platform = "MACOS"
  73. else:
  74. print_err("Platform 'Linux', 'Windows' or 'MacOS' expected")
  75. print_info(f"Platform: {OS_NAME}")
  76. exit()
  77. class Action():
  78. name: str
  79. kind: str
  80. extra: Dict
  81. toggled: bool
  82. delay: int
  83. max_delay: int
  84. def __init__(self, name, kind, extra=None, toggled=False, delay=0):
  85. self.name = name
  86. self.kind = kind
  87. if extra is not None:
  88. self.extra = extra
  89. else:
  90. self.extra = {}
  91. self.toggled = toggled
  92. self.delay = delay
  93. self.max_delay = delay
  94. def toggle(self) -> bool:
  95. self.toggled = not self.toggled
  96. return self.toggled
  97. def tick(self) -> bool:
  98. if not self.toggled:
  99. if self.delay != self.max_delay:
  100. self.delay = self.max_delay
  101. return False
  102. if self.max_delay != 0:
  103. self.delay -= 1
  104. if self.delay <= 0:
  105. self.delay = self.max_delay
  106. return True
  107. return False
  108. def do(self, delay: int=50, hold: int=20):
  109. if not self.toggled:
  110. return
  111. d = delay
  112. h = hold
  113. if self.extra is not None:
  114. if "delay" in self.extra:
  115. d = self.extra["delay"]
  116. if "hold" in self.extra:
  117. h = self.extra["hold"]
  118. if self.kind == "click":
  119. mouse_output(self.extra["button"], d, h)
  120. elif self.kind == "click down":
  121. mouse_down(self.extra["button"])
  122. elif self.kind == "key":
  123. keyboard_output(self.extra["write"], d, h)
  124. elif self.kind == "key down":
  125. key_down(self.extra["write"])
  126. class Tyrell():
  127. keybinds: Dict[str, Action]
  128. mirrors: Dict[str, str] # mirror key -> key in keybinds
  129. toggled: bool
  130. delay: int
  131. hold: int
  132. def __init__(self, profile_name: str):
  133. self.profile = {
  134. "activator": "`",
  135. "helper": "?",
  136. "delay": 50,
  137. "hold": 20,
  138. "tick": 50,
  139. "placeholder_name": False,
  140. }
  141. self.name = profile_name
  142. if profile_name.endswith(".toml"):
  143. self.name = profile_name.removesuffix(".toml")
  144. if not exists(self.name) or not isdir(self.name):
  145. print_err(f"Invalid profile '{self.name}'")
  146. print_info(f"This should be a directory")
  147. exit()
  148. profile_config = join(self.name, self.name+".toml")
  149. if exists(profile_config):
  150. with open(profile_config, "r") as f:
  151. try:
  152. t = toml_load(f)
  153. except Exception as err:
  154. print_err(f"Invalid profile '{self.name}'")
  155. print_info(f"Invalid '{self.name+'.toml'}' config")
  156. print(err)
  157. exit()
  158. self.profile = t
  159. if "activator" not in self.profile:
  160. self.profile["activator"] = "`"
  161. if "helper" not in self.profile:
  162. self.profile["helper"] = "?"
  163. if "hold" not in self.profile:
  164. self.profile["hold"] = 20
  165. if "delay" not in self.profile:
  166. self.profile["delay"] = 50
  167. if "tick" not in self.profile:
  168. self.profile["tick"] = 50
  169. if "placeholder_name" not in self.profile:
  170. self.profile["placeholder_name"] = False
  171. else:
  172. print_err(f"Invalid profile '{self.name}'")
  173. print_info(f"Missing '{self.name+'.toml'}' config")
  174. exit()
  175. self.delay = self.profile["delay"]
  176. self.hold = self.profile["hold"]
  177. self.keybinds = {}
  178. self.mirrors = {}
  179. self.toggled = False
  180. if not exists(join(self.name, 'keys')):
  181. print_err(f"Invalid profile '{self.name}'")
  182. print_info("Missing 'keys' directory (for keybinds)")
  183. exit()
  184. else:
  185. for ent in listdir(join(self.name, "keys")):
  186. if ent.startswith(".") or isdir(ent) or not ent.endswith(".toml"):
  187. continue
  188. with open(join(self.name, "keys", ent), "r") as f:
  189. t = toml_load(f)
  190. if t["keybind"] == "?" or t["keybind"] == "`":
  191. continue
  192. if "duration" in t:
  193. self.add_action(t["keybind"], Action(ent.removesuffix(".toml"), t["kind"], extra=t, delay=t["duration"]))
  194. else:
  195. self.add_action(t["keybind"], Action(ent.removesuffix(".toml"), t["kind"], extra=t))
  196. def toggle(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False) -> bool:
  197. self.toggled = not self.toggled
  198. if all:
  199. for key in self.keybinds:
  200. act = self.keybinds[key]
  201. act.toggle()
  202. elif all_tickers:
  203. for key in self.keybinds:
  204. act = self.keybinds[key]
  205. if act.max_delay != 0:
  206. act.toggle()
  207. elif all_notickers:
  208. for key in self.keybinds:
  209. act = self.keybinds[key]
  210. if act.max_delay == 0:
  211. act.toggle()
  212. return self.toggled
  213. def disable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  214. self.toggled = False
  215. if all:
  216. for key in self.keybinds:
  217. act = self.keybinds[key]
  218. act.toggled = False
  219. elif all_tickers:
  220. for key in self.keybinds:
  221. act = self.keybinds[key]
  222. if act.max_delay != 0:
  223. act.toggled = False
  224. elif all_notickers:
  225. for key in self.keybinds:
  226. act = self.keybinds[key]
  227. if act.max_delay == 0:
  228. act.toggled = False
  229. def enable(self, all: bool=False, all_tickers: bool=False, all_notickers: bool=False):
  230. self.toggled = True
  231. if all:
  232. for key in self.keybinds:
  233. act = self.keybinds[key]
  234. act.toggled = True
  235. elif all_tickers:
  236. for key in self.keybinds:
  237. act = self.keybinds[key]
  238. if act.max_delay != 0:
  239. act.toggled = True
  240. elif all_notickers:
  241. for key in self.keybinds:
  242. act = self.keybinds[key]
  243. if act.max_delay == 0:
  244. act.toggled = True
  245. def add_action(self, bind: str, act: Action):
  246. act.toggled = False
  247. if "placeholder_name" in self.profile:
  248. if self.profile["placeholder_name"] and "write" in act.extra:
  249. act.extra["write"] = act.extra["write"].replace("{name}", self.name)
  250. if act.kind == "mirror" and "mirror" in act.extra:
  251. self.mirrors[act.extra["mirror"]] = bind
  252. self.keybinds[bind] = act
  253. def remove_action(self, bind: str):
  254. self.keybinds[bind] = None
  255. def is_action(self, bind: str) -> bool:
  256. return self.keybinds[bind] is not None
  257. def print_help(self):
  258. print_ok(f"{int(1000 / self.profile['tick'])} ticks per second ({self.profile['tick']} ms per tick)")
  259. print_ok(f"Name placeholder: {self.profile['placeholder_name']}")
  260. print_warn(f"{self.profile['activator']} -> Activate/Deactivate")
  261. print_warn(f"{self.profile['helper']} -> Displays Help")
  262. for key in self.keybinds:
  263. act = self.keybinds[key]
  264. if act.max_delay == 0 and act.toggled:
  265. print_info(f"{key} -> {act.name}")
  266. else:
  267. print_info(f"{key} -> {act.name} = {act.toggled} ({act.max_delay} ticks)")
  268. print_ok("Please use " + style("CTRL+C", fg="bright_yellow") + " to stop")
  269. def tick(self):
  270. for key in self.keybinds:
  271. act = self.keybinds[key]
  272. if act.tick():
  273. act.do(self.delay, self.hold)
  274. # def callback(self, event: KeyboardEvent):
  275. # key_name = event.name
  276. # #if key_name in self.mirrors:
  277. # # # key mirrors currently lag
  278. # # act = self.keybinds[self.mirrors[key_name]]
  279. # # if act.toggled:
  280. # # if "write" in act.extra:
  281. # # if event.event_type == "down":
  282. # # key_down(act.extra["write"])
  283. # # else:
  284. # # key_up(act.extra["write"])
  285. # # elif "button" in act.extra:
  286. # # if event.event_type == "down":
  287. # # mouse_down(button=act.extra["button"])
  288. # # else:
  289. # # mouse_up(button=act.extra["button"])
  290. # #if event.event_type == "up":
  291. # if key_name == self.profile["activator"]:
  292. # if self.toggle():
  293. # print_ok("ON")
  294. # else:
  295. # print_ok("OFF")
  296. # elif self.toggled:
  297. # #print(f"Name: {event.name}")
  298. # #print(f"Code: {event.scan_code}")
  299. # #print(f"Modifiers: {event.modifiers}")
  300. # if self.profile["helper"] == "?" and "shift" in event.modifiers and event.name == "/" or key_name == self.profile["helper"]:
  301. # self.print_help()
  302. # elif key_name in self.keybinds:
  303. # act = self.keybinds[key_name]
  304. # if act.max_delay == 0 and act.toggled:
  305. # print_info(f"{key_name} -> {act.kind}")
  306. # act.do(self.delay, self.hold)
  307. # else:
  308. # print_info(f"{key_name} -> {act.kind} = {act.toggle()}")
  309. # self.toggle()
  310. # print_ok("OFF")
  311. def callback(self, bind, state=None, mirror_host=None):
  312. if bind == "__toggle__":
  313. if self.toggle():
  314. print_info(style("ON", fg="bright_green"))
  315. else:
  316. print_info(style("OFF", fg="bright_red"))
  317. return
  318. if state == "up" or state == "down":
  319. act = self.keybinds[mirror_host]
  320. if act.toggled:
  321. if act.extra["write"] != None or len(act.extra["write"]) != 0:
  322. if state == "up":
  323. print(f"'{bind}' -> {act.name} UP")
  324. key_up(act.extra["write"])
  325. elif state == "down":
  326. print(f"'{bind}' -> {act.name} DOWN")
  327. key_down(act.extra["write"])
  328. elif act.extra["button"] != None or len(act.extra["button"]) != 0:
  329. if state == "up":
  330. print(f"'{bind}' -> {act.name} UP")
  331. mouse_up(button=act.extra["button"])
  332. elif state == "down":
  333. print(f"'{bind}' -> {act.name} DOWN")
  334. mouse_down(button=act.extra["button"])
  335. return
  336. if not self.toggled:
  337. return
  338. if bind == "__help__":
  339. self.print_help()
  340. self.disable()
  341. print_info(style("OFF", fg="bright_red"))
  342. return
  343. act = self.keybinds[bind]
  344. if act.toggled:
  345. print(f"'{bind}' -> {act.name}")
  346. act.do(self.delay, self.hold)
  347. elif act.max_delay != 0 or act.kind == "mirror":
  348. if act.toggle():
  349. print(f"'{bind}' -> {act.name} " + style("ON", fg="bright_green"))
  350. else:
  351. print(f"'{bind}' -> {act.name} " + style("OFF", fg="bright_red"))
  352. self.disable()
  353. print_info(style("OFF", fg="bright_red"))
  354. async def background(self):
  355. while True:
  356. self.tick()
  357. await asleep(self.profile["tick"] / 1000) # 50 ms (20 per second, same as Minecraft)
  358. async def mainloop(self):
  359. #keyboard_hook(self.callback)
  360. add_hotkey(41, self.callback, args=["__toggle__"])
  361. add_hotkey("shift+?", self.callback, args=["__help__"])
  362. for bind in self.keybinds:
  363. act = self.keybinds[bind]
  364. if act.kind == "key" or act.kind == "key down":
  365. print(f"hotkey -=> {bind}")
  366. add_hotkey(bind, self.callback, args=[bind])
  367. elif act.kind == "mirror":
  368. print(f"hotkey -=> {bind} (mirror {act.extra['mirror']})")
  369. add_hotkey(bind, self.callback, args=[bind])
  370. add_hotkey(bind, self.callback, args=[act.extra["mirror"], "down", bind])
  371. add_hotkey(bind, self.callback, args=[act.extra["mirror"], "up", bind], trigger_on_release=True)
  372. await task_group(
  373. self.background()
  374. )
  375. if __name__ == "__main__":
  376. if len(argv) == 1:
  377. print_err("Missing profile filename")
  378. print_info("Example: 'tyrell.py Apollo' would look for 'Apollo.toml'")
  379. print()
  380. print_info("Profiles allow you to add placeholders,")
  381. print_info("And defines in a \"global\" sense key delay and hold delay.")
  382. print()
  383. exit()
  384. ty = Tyrell(",".join(argv[1:]))
  385. if len(ty.keybinds) == 0:
  386. print_err("Missing keybinds")
  387. print_info("(Might want some keybinds, place them in a 'keys' directory)")
  388. print_info(f"( Need an example? Look at '{join('_example', 'keys', 'example.toml')}')")
  389. exit()
  390. ty.enable(all_notickers=True)
  391. ty.disable()
  392. ty.print_help()
  393. if ty.name == "_example":
  394. print_warn("This is the " + style("example", fg="bright_yellow") + ", please define you're own profile")
  395. print_info("Please DO NOT EDIT this example profile")
  396. try:
  397. run(ty.mainloop())
  398. except KeyboardInterrupt:
  399. exit()