tyrell.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. from sys import argv as _argv
  2. from os import name as _OS_NAME, environ as _env
  3. from os import listdir as _listdir, mkdir as _mkdir, chown as _chown
  4. from os.path import exists as _exists, join as _join, isdir as _isdir
  5. from time import sleep as _sleep
  6. from typing import Dict as _Dict, Tuple as _Tuple, List as _List
  7. try:
  8. from click import echo as _echo, style as _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 as _KeyboardEvent, add_hotkey as _add_hotkey, hook as _key_hook, 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 as _run, sleep as _asleep
  18. from asyncio.tasks import gather as _task_group
  19. def _print_err(msg: str):
  20. _echo(_style("ERROR", fg="bright_red") + " " + msg)
  21. def _print_warn(msg: str):
  22. _echo(_style("WARN ", fg="bright_yellow") + " " + msg)
  23. def _print_ok(msg: str):
  24. _echo(_style(" OK ", fg="bright_green") + " " + msg)
  25. def _print_info(msg: str):
  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. def _mouse_output(button: str, delay: int=50, hold: int=20):
  31. _mouse_down(button=button)
  32. _sleep(hold / 1000)
  33. _mouse_up(button=button)
  34. _sleep(hold / 1000)
  35. _sleep(delay / 1000)
  36. platform: str = ""
  37. os_name = _OS_NAME.lower()
  38. if "linux" in os_name or "posix" in os_name:
  39. platform = "LINUX"
  40. elif "windows" in os_name or "nt" in os_name:
  41. platform = "WINDOWS"
  42. elif "darwin" in os_name:
  43. platform = "MACOS"
  44. else:
  45. _print_err("Platform 'Linux', 'Windows' or 'MacOS' expected")
  46. _print_info(f"Platform: {_OS_NAME}")
  47. exit()
  48. def _profile_pre_check(profile_name: str):
  49. if profile_name.endswith(".toml"):
  50. profile_name = profile_name.removesuffix(".toml")
  51. if not _exists(profile_name):
  52. _mkdir(profile_name)
  53. if "SUDO_USER" in _env:
  54. _chown(profile_name, int(_env['SUDO_UID']), int(_env['SUDO_GID']))
  55. if not _exists(_join(profile_name, "keys")):
  56. _mkdir(_join(profile_name, "keys"))
  57. if "SUDO_USER" in _env:
  58. _chown(_join(profile_name, "keys"), int(_env['SUDO_UID']), int(_env['SUDO_GID']))
  59. def _write_profile(profile_name: str):
  60. if profile_name.endswith(".toml"):
  61. profile_name = profile_name.removesuffix(".toml")
  62. _profile_pre_check(profile_name)
  63. with open(_join(profile_name, profile_name+".toml"), "w") as f:
  64. f.writelines("\n".join([
  65. '# Tyrell :: v0.1-dev :: More human than human',
  66. '',
  67. '# Placeholders',
  68. '',
  69. "# Enable '{name}', emitting profile name",
  70. "placeholder_name = 'true'",
  71. '',
  72. '# Tick speed',
  73. '# Value in milliseconds',
  74. "# Defaults to 20 ticks per second",
  75. "# Can't be overridden",
  76. 'tick = 50',
  77. '',
  78. '# What is the key to activate/deactivate Tyrell',
  79. "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
  80. "# Can't be overridden",
  81. "activator = '`'",
  82. '',
  83. '# What is the key to display help',
  84. "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
  85. "# Defaults to '?'",
  86. "# Can't be overridden",
  87. "helper = 'shift+/'",
  88. '',
  89. '# These can be overridden per key-bind',
  90. '',
  91. '# Delay between each keys',
  92. '# Value in milliseconds',
  93. 'delay = 50',
  94. '',
  95. '# Hold delay for each key',
  96. '# Keys are sent like below:',
  97. '# 1. key down',
  98. '# 2. hold delay',
  99. '# 3. key up',
  100. '# 4. hold delay',
  101. '# 5. delay (see above)',
  102. '# 6. Repeat for keys',
  103. '# Value in milliseconds',
  104. 'hold = 20',
  105. ]))
  106. if "SUDO_USER" in _env:
  107. _chown(_join(profile_name, profile_name+".toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID']))
  108. def _write_example(profile_name: str):
  109. if profile_name.endswith(".toml"):
  110. profile_name = profile_name.removesuffix(".toml")
  111. _profile_pre_check(profile_name)
  112. with open(_join(profile_name, "keys", "example.toml"), "w") as f:
  113. f.writelines("\n".join([
  114. '# Example Key-bind',
  115. '',
  116. "# When 'h' is pressed, write 'Hello World!'",
  117. '',
  118. '# Key to trigger on',
  119. "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
  120. "key = 'h'",
  121. '',
  122. '# Kind of action to perform',
  123. '# click -> Click a mouse button',
  124. '# click down -> Click a mouse button down, but never release up',
  125. '# key -> Type the following keys, pressing each key',
  126. '# key down -> Press the following keys down, never releasing them',
  127. '# mirror -> When the mirror key is pressed down so is key or button, on release so are key or button',
  128. '# See also example_mirror.toml',
  129. "kind = 'key'",
  130. '',
  131. '# Type this',
  132. "# This is required for 'key' and 'key down' kinds (Optional for 'mirror')",
  133. "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
  134. "# This example will write 'Hello World!'",
  135. "write = 'shift+h, e, l, l, o, space, shift+w, o, r, l, d, shift+1'",
  136. '',
  137. '# If this should repeat X number of ticks',
  138. "# This is a simple example, it won't use this",
  139. '# Assuming tick speed is 20ms, this would occur every second',
  140. '#duration = 50',
  141. '',
  142. '# Use this mouse button',
  143. "# This is a simple example, it won't use this",
  144. "# This is required for 'click' and 'click down' kinds (Optional for 'mirror')",
  145. "# There currently is 'left' or 'right'",
  146. "#button = 'left'",
  147. '',
  148. '# Mirror key',
  149. "# This is a simple example, it won't use this",
  150. '# This is the key whose state will be mirrored',
  151. '# See also example_mirror.toml',
  152. '#',
  153. "# This hotkey must be in the format, 'ctrl+shift+a' (user holds ctrl, shift and 'a' at once)",
  154. "# This example will press 'g' when 'h' is down, and release 'g' when 'h' is up",
  155. "#mirror = 'g'",
  156. '',
  157. '# Optional Overrides',
  158. '',
  159. '# Override delay between each keys',
  160. '# Value in milliseconds',
  161. 'delay = 50',
  162. '',
  163. '# Override hold delay for each key',
  164. '# Keys are sent like below:',
  165. '# 1. key down',
  166. '# 2. hold delay',
  167. '# 3. key up',
  168. '# 4. hold delay',
  169. '# 5. delay (see above)',
  170. '# 6. Repeat for keys',
  171. '# Value in milliseconds',
  172. 'hold = 20',
  173. ]))
  174. if "SUDO_USER" in _env:
  175. _chown(_join(profile_name, "keys", "example.toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID']))
  176. def _write_example_mirror(profile_name: str):
  177. if profile_name.endswith(".toml"):
  178. profile_name = profile_name.removesuffix(".toml")
  179. _profile_pre_check(profile_name)
  180. with open(_join(profile_name, "keys", "example_mirror.toml"), "w") as f:
  181. f.writelines("\n".join([
  182. '# Example Mirror Key-bind',
  183. '',
  184. "# Press alt when 'w' is down, and release when 'w' is up",
  185. "# Use 'z' to toggle on/off",
  186. '',
  187. '# Key to toggle',
  188. "key = 'z'",
  189. '',
  190. '# Kind of event',
  191. "kind = 'mirror'",
  192. '',
  193. '# Mirror what key',
  194. "mirror = 'w'",
  195. '',
  196. '# When mirror is down, so will this',
  197. '# When mirror is up, so will this',
  198. "write = 'alt'",
  199. '',
  200. '# Or a mouse button could be configured',
  201. "# But this example doesn't",
  202. "#button = 'right'",
  203. '',
  204. '# You could override delay and hold',
  205. '# Tyrell will turn them off for Mirror kinds automatically',
  206. ]))
  207. if "SUDO_USER" in _env:
  208. _chown(_join(profile_name, "keys", "example_mirror.toml"), int(_env['SUDO_UID']), int(_env['SUDO_GID']))
  209. class Profile:
  210. name: str
  211. placeholders: _Dict[str, bool]
  212. tick: int
  213. delay: int
  214. hold: int
  215. activator: str
  216. helper: str
  217. def __init__(self, name: str):
  218. # Unify name without extension
  219. if name.endswith(".toml"):
  220. self.name = name.removesuffix(".toml")
  221. else:
  222. self.name = name
  223. # Pre-Check
  224. if not _exists(self.name):
  225. # Does not exist, new everything
  226. _write_profile(self.name)
  227. _write_example(self.name)
  228. _write_example_mirror(self.name)
  229. else:
  230. # Profile config?
  231. if not _exists(_join(self.name, self.name+".toml")):
  232. _write_profile(self.name)
  233. # Do we have keys?
  234. if not _exists(_join(self.name, "keys")):
  235. _write_example(self.name)
  236. _write_example_mirror(self.name)
  237. # Initial load
  238. self.reload()
  239. def reload(self):
  240. # Load toml config
  241. with open(_join(self.name, self.name+".toml"), "r") as f:
  242. dat = _toml_load(f)
  243. self.placeholders = {
  244. "name": False,
  245. }
  246. self.tick = 50
  247. self.activator = "`"
  248. self.helper = "shift+/"
  249. self.delay = 50
  250. self.hold = 20
  251. for key in dat:
  252. val = dat[key]
  253. if key.startswith("placeholder_"):
  254. if str(val) in ('true', 'TRUE', 'True', 'T', 't', 'yes', 'YES', 'Yes', 'Y', 'y', '1'):
  255. self.placeholders[key.removeprefix("placeholder_")] = True
  256. else:
  257. self.placeholders[key.removeprefix("placeholder_")] = False
  258. elif key == "tick":
  259. self.tick = int(val)
  260. elif key == "delay":
  261. self.delay = int(val)
  262. elif key == "hold":
  263. self.hold = int(val)
  264. elif key == "activator":
  265. self.activator = str(val)
  266. elif key == "helper":
  267. self.helper = str(val)
  268. if __name__ == "__main__":
  269. if not _exists("_example"):
  270. p = Profile("_example")
  271. exit()
  272. _print_ok("All okay")