Skip to content

Commit

Permalink
Merge pull request #302 from spyoungtech/gh-301
Browse files Browse the repository at this point in the history
GH-301 prevent exception when adding hotkeys after hotkeys are started
  • Loading branch information
spyoungtech authored May 1, 2024
2 parents 168c753 + 033e813 commit 4007bb8
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 76 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ repos:
- id: trailing-whitespace
- id: double-quote-string-fixer
- repo: https://github.com/psf/black
rev: '24.4.0'
rev: '24.4.2'
hooks:
- id: black
args:
Expand All @@ -45,7 +45,7 @@ repos:
- id: reorder-python-imports

- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.9.0'
rev: 'v1.10.0'
hooks:
- id: mypy
args:
Expand Down
40 changes: 17 additions & 23 deletions ahk/_async/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,29 @@
from .._utils import MsgBoxOtherOptions
from .._utils import type_escape
from ..directives import Directive

if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias

from ..extensions import (
Extension,
_ExtensionMethodRegistry,
_extension_registry,
_resolve_extensions,
)
from ..extensions import _extension_registry
from ..extensions import _ExtensionMethodRegistry
from ..extensions import _resolve_extensions
from ..extensions import Extension
from ..keys import Key
from .transport import AsyncDaemonProcessTransport
from .transport import AsyncFutureResult
from .transport import AsyncTransport
from .window import AsyncControl
from .window import AsyncWindow
from ahk._types import _BUTTONS
from ahk._types import Coordinates
from ahk._types import CoordModeRelativeTo
from ahk._types import CoordModeTargets
from ahk._types import MouseButton
from ahk._types import Position
from ahk._types import SendMode
from ahk._types import TitleMatchMode

from ahk._types import (
Position,
CoordModeTargets,
CoordModeRelativeTo,
TitleMatchMode,
_BUTTONS,
MouseButton,
SendMode,
Coordinates,
)
if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias

async_sleep = asyncio.sleep # unasync: remove
sleep = time.sleep
Expand Down Expand Up @@ -92,7 +86,7 @@ def _resolve_button(button: Union[str, int]) -> str:
resolved_button = _BUTTONS[button]
elif isinstance(button, int) and button > 3:
# for addtional mouse buttons
resolved_button = f'X{button-3}'
resolved_button = f'X{button - 3}'
else:
assert isinstance(button, str)
resolved_button = button
Expand Down
5 changes: 3 additions & 2 deletions ahk/_async/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from ahk._types import FunctionName
from ahk._types import Position
from ahk._utils import _version_detection_script
from ahk._utils import try_remove
from ahk.directives import Directive
from ahk.exceptions import AHKProtocolError
from ahk.extensions import _resolve_includes
Expand Down Expand Up @@ -668,15 +669,15 @@ async def _create_process(
tempscriptfile.write(script_text) # XXX: can we make this async?
self._temp_script = tempscriptfile.name
daemon_script = self._temp_script
atexit.register(os.remove, tempscriptfile.name)
atexit.register(try_remove, tempscriptfile.name)
else:
daemon_script = self._temp_script
else:
script_text = self._render_script(template=template, **template_kwargs)
with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-', suffix='.ahk', delete=False) as tempscript:
tempscript.write(script_text)
daemon_script = tempscript.name
atexit.register(os.remove, tempscript.name)
atexit.register(try_remove, tempscript.name)
runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script]
proc = AsyncAHKProcess(runargs=runargs)
await proc.start()
Expand Down
67 changes: 34 additions & 33 deletions ahk/_hotkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

import atexit
import functools
import os
import logging
import re
import subprocess
import sys
import tempfile
import threading
import time
import warnings
from abc import ABC
from abc import abstractmethod
from base64 import b64encode
from queue import Queue
from typing import Any
from typing import Callable
from typing import Dict
Expand All @@ -26,21 +28,18 @@

import jinja2

from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT
from ._constants import HOTKEYS_SCRIPT_V2_TEMPLATE as _HOTKEY_V2_SCRIPT
from .directives import Directive
from ahk._utils import hotkey_escape
from ahk._utils import try_remove

if sys.version_info >= (3, 10):
from typing import ParamSpec
else:
from typing_extensions import ParamSpec


import logging
import tempfile
from queue import Queue

from ahk._utils import hotkey_escape
from ._constants import HOTKEYS_SCRIPT_TEMPLATE as _HOTKEY_SCRIPT, HOTKEYS_SCRIPT_V2_TEMPLATE as _HOTKEY_V2_SCRIPT

P_HotkeyCallbackParam = ParamSpec('P_HotkeyCallbackParam')
T_HotkeyCallbackReturn = TypeVar('T_HotkeyCallbackReturn')

Expand Down Expand Up @@ -324,31 +323,33 @@ def _render_hotkey_template(self) -> str:
def listener(self) -> None:
hotkey_script_contents = self._render_hotkey_template()
logging.debug('hotkey script contents:\n%s', hotkey_script_contents)
with tempfile.TemporaryDirectory(prefix='python-ahk') as tmpdirname:
exc_file = os.path.join(tmpdirname, 'executor.ahk')
with open(exc_file, 'w') as f:
f.write(hotkey_script_contents)
self._proc = subprocess.Popen(
[self._executable_path, '/CP65001', '/ErrorStdOut', exc_file],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
atexit.register(kill, self._proc)
assert self._proc.stdout is not None
assert self._proc.stdin is not None
while self._running:
line = self._proc.stdout.readline()
if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL:
logging.debug('keepalive received')
self._proc.stdin.write(b'\xee\x80\x80\n')
self._proc.stdin.flush()
continue
if not line.strip():
logging.debug('Listener: Process probably died, exiting')
break
logging.debug(f'Received {line!r}')
self._callback_queue.put_nowait(line.decode('UTF-8').strip())
with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-hotkeys-', suffix='.ahk', delete=False) as f:
f.write(hotkey_script_contents)
atexit.register(try_remove, f.name)
self._proc = subprocess.Popen(
[self._executable_path, '/CP65001', '/ErrorStdOut', f.name],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
atexit.register(kill, self._proc)
assert self._proc.stdout is not None
assert self._proc.stdin is not None
while self._running:
line = self._proc.stdout.readline()
if line.rstrip(b'\n') == _KEEPALIVE_SENTINEL:
logging.debug('keepalive received')
self._proc.stdin.write(b'\xee\x80\x80\n')
self._proc.stdin.flush()
continue
if not line.strip():
logging.debug('Listener: Process probably died, exiting')
break
logging.debug(f'Received {line!r}')
self._callback_queue.put_nowait(line.decode('UTF-8').strip())
# although redundant with the atexit handler, this will prevent
# excessive use of disk space in cases where the hotkey process is [re]started many times
try_remove(f.name)


class Hotkey:
Expand Down
31 changes: 17 additions & 14 deletions ahk/_sync/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,29 @@
from .._utils import MsgBoxOtherOptions
from .._utils import type_escape
from ..directives import Directive

if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias

from ..extensions import (
Extension,
_ExtensionMethodRegistry,
_extension_registry,
_resolve_extensions,
)
from ..extensions import _extension_registry
from ..extensions import _ExtensionMethodRegistry
from ..extensions import _resolve_extensions
from ..extensions import Extension
from ..keys import Key
from .transport import DaemonProcessTransport
from .transport import FutureResult
from .transport import Transport
from .window import Control
from .window import Window
from ahk._types import _BUTTONS
from ahk._types import Coordinates
from ahk._types import CoordModeRelativeTo
from ahk._types import CoordModeTargets
from ahk._types import MouseButton
from ahk._types import Position
from ahk._types import SendMode
from ahk._types import TitleMatchMode

from ahk._types import Position, CoordModeTargets, CoordModeRelativeTo, TitleMatchMode, _BUTTONS, MouseButton, SendMode, Coordinates
if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias

sleep = time.sleep

Expand All @@ -79,7 +82,7 @@ def _resolve_button(button: Union[str, int]) -> str:
resolved_button = _BUTTONS[button]
elif isinstance(button, int) and button > 3:
# for addtional mouse buttons
resolved_button = f'X{button-3}'
resolved_button = f'X{button - 3}'
else:
assert isinstance(button, str)
resolved_button = button
Expand Down
5 changes: 3 additions & 2 deletions ahk/_sync/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from ahk._types import FunctionName
from ahk._types import Position
from ahk._utils import _version_detection_script
from ahk._utils import try_remove
from ahk.directives import Directive
from ahk.exceptions import AHKProtocolError
from ahk.extensions import _resolve_includes
Expand Down Expand Up @@ -632,15 +633,15 @@ def _create_process(
tempscriptfile.write(script_text) # XXX: can we make this async?
self._temp_script = tempscriptfile.name
daemon_script = self._temp_script
atexit.register(os.remove, tempscriptfile.name)
atexit.register(try_remove, tempscriptfile.name)
else:
daemon_script = self._temp_script
else:
script_text = self._render_script(template=template, **template_kwargs)
with tempfile.NamedTemporaryFile(mode='w', prefix='python-ahk-', suffix='.ahk', delete=False) as tempscript:
tempscript.write(script_text)
daemon_script = tempscript.name
atexit.register(os.remove, tempscript.name)
atexit.register(try_remove, tempscript.name)
runargs = [self._executable_path, '/CP65001', '/ErrorStdOut', daemon_script]
proc = SyncAHKProcess(runargs=runargs)
proc.start()
Expand Down
8 changes: 8 additions & 0 deletions ahk/_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import enum
import logging
import os
import re
import subprocess
Expand Down Expand Up @@ -170,3 +171,10 @@ def _get_executable_major_version(executable_path: str) -> Literal['v1', 'v2']:
return 'v2'
else:
raise ValueError(f'Unexpected version {version!r}')


def try_remove(name: str) -> None:
try:
os.remove(name)
except Exception as e:
logging.debug(f'Ignoring removal exception {e}')

0 comments on commit 4007bb8

Please sign in to comment.