-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from spyoungtech/develop
v0.1.1
- Loading branch information
Showing
16 changed files
with
597 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
from ahk.autohotkey import AHK | ||
__all__ = ['AHK'] | ||
__all__ = ['AHK'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,92 +1,6 @@ | ||
from tempfile import NamedTemporaryFile | ||
import os | ||
import ast | ||
import subprocess | ||
import shutil | ||
from textwrap import dedent | ||
import time | ||
from contextlib import suppress | ||
import logging | ||
from ahk.mouse import MouseMixin | ||
from ahk.window import WindowMixin | ||
|
||
class AHK(object): | ||
def __init__(self, executable_path: str='', keep_scripts: bool=False): | ||
""" | ||
|
||
:param executable_path: the path to the AHK executable. Defaults to environ['AHK_PATH'] otherwise tries 'AutoHotkeyA32.exe' | ||
:param keep_scripts: | ||
:raises RuntimeError: if AHK executable is not provided and cannot be found in environment variables or PATH | ||
""" | ||
self.speed = 2 | ||
self.keep_scripts = bool(keep_scripts) | ||
executable_path = executable_path or os.environ.get('AHK_PATH') or shutil.which('AutoHotkey.exe') or shutil.which('AutoHotkeyA32.exe') | ||
self.executable_path = executable_path | ||
|
||
def _run_script(self, script_path, **kwargs): | ||
result = subprocess.run([self.executable_path, script_path], stdin=None, stderr=None, stdout=subprocess.PIPE, **kwargs) | ||
return result.stdout.decode() | ||
|
||
def run_script(self, script_text:str, delete=None, blocking=True, **runkwargs): | ||
if blocking is False: | ||
script_text = script_text.lstrip('#Persistent') | ||
if delete is None: | ||
delete = not self.keep_scripts | ||
try: | ||
with NamedTemporaryFile(mode='w', delete=False) as temp_script: | ||
temp_script.write(script_text) | ||
result = self._run_script(temp_script.name) | ||
except Exception as e: | ||
logging.critical('Something went terribly wrong: %s', e) | ||
result = None | ||
finally: | ||
if delete: | ||
with suppress(OSError): | ||
os.remove(temp_script.name) | ||
return result | ||
|
||
def _mouse_position(self): | ||
return dedent(''' | ||
#Persistent | ||
MouseGetPos, xpos, ypos | ||
s .= Format("({}, {})", xpos, ypos) | ||
FileAppend, %s%, * | ||
ExitApp | ||
''') | ||
|
||
@property | ||
def mouse_position(self): | ||
response = self.run_script(self._mouse_position()) | ||
return ast.literal_eval(response) | ||
|
||
@mouse_position.setter | ||
def mouse_position(self, position): | ||
x, y = position | ||
self.move_mouse(x=x, y=y, speed=0, relative=False) | ||
|
||
def _move_mouse(self, x=None, y=None, speed=None, relative=False): | ||
if x is None and y is None: | ||
raise ValueError('Position argument(s) missing. Must provide x and/or y coordinates') | ||
if speed is None: | ||
speed = self.speed | ||
if relative and (x is None or y is None): | ||
x = x or 0 | ||
y = y or 0 | ||
elif not relative and (x is None or y is None): | ||
posx, posy = self.mouse_position | ||
x = x or posx | ||
y = y or posy | ||
|
||
if relative: | ||
relative = ', R' | ||
else: | ||
relative = '' | ||
script = dedent(f'''\ | ||
#Persistent | ||
MouseMove, {x}, {y} , {speed}{relative} | ||
ExitApp | ||
''') | ||
return script | ||
|
||
def move_mouse(self, *args, **kwargs): | ||
script = self._move_mouse(*args, **kwargs) | ||
response = self.run_script(script_text=script) | ||
return response or None | ||
class AHK(WindowMixin, MouseMixin): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
from types import SimpleNamespace | ||
|
||
|
||
class DirectiveMeta(type): | ||
""" | ||
Overrides __str__ so directives with no arguments can be used without instantiation | ||
Overrides __hash__ to make objects 'unique' based upon a hash of the str representation | ||
""" | ||
def __str__(cls): | ||
return f"#{cls.__name__}" | ||
|
||
def __hash__(self): | ||
return hash(str(self)) | ||
|
||
def __eq__(cls, other): | ||
return str(cls) == other | ||
|
||
|
||
class Directive(SimpleNamespace, metaclass=DirectiveMeta): | ||
""" | ||
Simple directive class | ||
They are designed to be hashable and comparable with string equivalent of AHK directive. | ||
Directives that don't require arguments do not need to be instantiated. | ||
""" | ||
def __init__(self, **kwargs): | ||
super().__init__(name=self.name, **kwargs) | ||
self._kwargs = kwargs | ||
|
||
@property | ||
def name(self): | ||
return self.__class__.__name__ | ||
|
||
def __str__(self): | ||
if self._kwargs: | ||
arguments = ' '.join(str(value) for key, value in self._kwargs.items()) | ||
else: | ||
arguments = '' | ||
return f"#{self.name} {arguments}".rstrip() | ||
|
||
def __eq__(self, other): | ||
return str(self) == other | ||
|
||
def __hash__(self): | ||
return hash(str(self)) | ||
|
||
|
||
class AllowSameLineComments(Directive): | ||
pass | ||
|
||
|
||
class ClipboardTimeout(Directive): | ||
def __init__(self, milliseconds=0, **kwargs): | ||
kwargs['milliseconds'] = milliseconds | ||
super().__init__(**kwargs) | ||
|
||
|
||
class ErrorStdOut(Directive): | ||
pass | ||
|
||
|
||
class HotKeyInterval(ClipboardTimeout): | ||
pass | ||
|
||
|
||
class HotKeyModifierTimeout(HotKeyInterval): | ||
pass | ||
|
||
|
||
class Include(Directive): | ||
def __init__(self, include_name, **kwargs): | ||
kwargs['include_name'] = include_name | ||
super().__init__(**kwargs) | ||
|
||
|
||
class IncludeAgain(Include): | ||
pass | ||
|
||
|
||
class InputLevel(Directive): | ||
def __init__(self, level, **kwargs): | ||
kwargs['level'] = level | ||
super().__init__(**kwargs) | ||
|
||
|
||
class InstallKeybdHook(Directive): | ||
pass | ||
|
||
|
||
class InstallMouseHook(Directive): | ||
pass | ||
|
||
|
||
class KeyHistory(Directive): | ||
def __init__(self, limit=40, **kwargs): | ||
kwargs['limit'] = limit | ||
super().__init__(**kwargs) | ||
|
||
|
||
class MaxHotkeysPerInterval(Directive): | ||
def __init__(self, value, **kwargs): | ||
kwargs['value'] = value | ||
super().__init__(**kwargs) | ||
|
||
|
||
class MaxMem(Directive): | ||
def __init__(self, megabytes: int, **kwargs): | ||
if megabytes < 1: | ||
raise ValueError('megabytes cannot be less than 1') | ||
if megabytes > 4095: | ||
raise ValueError('megabytes cannot exceed 4095') | ||
kwargs['megabytes'] = megabytes | ||
super().__init__(**kwargs) | ||
|
||
|
||
class MaxThreads(Directive): | ||
def __init__(self): | ||
raise NotImplemented | ||
|
||
|
||
class MaxThreadsBuffer(Directive): | ||
def __init__(self): | ||
raise NotImplemented | ||
|
||
|
||
class MaxThreadsPerHotkey(Directive): | ||
def __init__(self): | ||
raise NotImplemented | ||
|
||
|
||
class MenuMaskKey(Directive): | ||
def __init__(self): | ||
raise NotImplemented | ||
|
||
|
||
class NoEnv(Directive): | ||
pass | ||
|
||
|
||
class NoTrayIcon(Directive): | ||
pass | ||
|
||
|
||
class Persistent(Directive): | ||
pass | ||
|
||
|
||
class SingleInstance(Directive): | ||
pass | ||
|
||
|
||
class UseHook(Directive): | ||
pass | ||
|
||
|
||
class Warn(Directive): | ||
pass | ||
|
||
|
||
class WinActivateForce(Directive): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from ahk.utils import make_script | ||
from ahk.script import ScriptEngine | ||
import ast | ||
|
||
|
||
class MouseMixin(ScriptEngine): | ||
def __init__(self, mouse_speed=2, mode=None, **kwargs): | ||
if mode is None: | ||
mode = 'Screen' | ||
self.mode = mode | ||
self._mouse_speed = mouse_speed | ||
super().__init__(**kwargs) | ||
|
||
@property | ||
def mouse_speed(self): | ||
if callable(self._mouse_speed): | ||
return self._mouse_speed() | ||
else: | ||
return self._mouse_speed | ||
|
||
|
||
def _mouse_position(self, mode=None): | ||
if mode is None: | ||
mode = self.mode | ||
return make_script(f''' | ||
CoordMode, Mouse, {mode} | ||
MouseGetPos, xpos, ypos | ||
s .= Format("({{}}, {{}})", xpos, ypos) | ||
FileAppend, %s%, * | ||
''') | ||
|
||
@property | ||
def mouse_position(self): | ||
response = self.run_script(self._mouse_position()) | ||
return ast.literal_eval(response) | ||
|
||
@mouse_position.setter | ||
def mouse_position(self, position): | ||
x, y = position | ||
self.mouse_move(x=x, y=y, speed=0, relative=False) | ||
|
||
def _mouse_move(self, x=None, y=None, speed=None, relative=False, mode=None): | ||
if x is None and y is None: | ||
raise ValueError('Position argument(s) missing. Must provide x and/or y coordinates') | ||
if speed is None: | ||
speed = self.mouse_speed | ||
if mode is None: | ||
mode = self.mode | ||
if relative and (x is None or y is None): | ||
x = x or 0 | ||
y = y or 0 | ||
elif not relative and (x is None or y is None): | ||
posx, posy = self.mouse_position | ||
x = x or posx | ||
y = y or posy | ||
|
||
if relative: | ||
relative = ', R' | ||
else: | ||
relative = '' | ||
script = make_script(f''' | ||
CoordMode Mouse, {mode} | ||
MouseMove, {x}, {y} , {speed}{relative} | ||
''') | ||
return script | ||
|
||
def mouse_move(self, *args, **kwargs): | ||
script = self._mouse_move(*args, **kwargs) | ||
self.run_script(script_text=script) |
Oops, something went wrong.