Lightweight execution and bidirectional communication for native AutoHotkey code and scripts; full language support.
Project description
ahkUnwrapped
Program in Python; use AutoHotkey for its simplification and mastery of the Windows API.
Features
-
Lightweight single module:
- 170 lines of AutoHotkey.
-
Performance is a feature.
-
Zero wrappers or abstractions.
-
GPL licensed to bake-in AutoHotkey.
-
Test suite for all types (primitives).
-
Supports exceptions and hard exits.
-
Execute arbitrary AHK or load from files.
-
Errors for unsupported values (
NaN
Inf
\0
). -
Warnings for loss of data (maximum 6 decimal places).
-
Supports PyInstaller for single .exe bundles.
-
Full language support; all of AutoHotkey:
- Easy mouse and keyboard events.
- Detect and manipulate windows.
- WinAPI calls with minimal code.
- Rapid GUI layouts.
How it Works
Each Script
launches an AutoHotkey.exe process with framework and user code passed via stdin. The framework listens for windows messages from Python and responds via stdout.
Usage
call(proc, ...)
f(func, ...)
get(var)
set(var, val)
from ahkunwrapped import Script
ahk = Script()
isNotepadActive = ahk.f('WinActive', "ahk_class Notepad") # built-in functions are directly callable
print(isNotepadActive)
from ahkunwrapped import Script
ahk = Script('''
WinMinimize(winTitle) {
WinMinimize, % winTitle
}
''')
ahk.call('WinMinimize', "ahk_class Notepad") # built-in commands can be used from functions
from pathlib import Path
from ahkunwrapped import Script
ahk = Script.from_file(Path('my_msg.ahk')) # load from a file
ahk.call('MyMsg', "Wooo!")
my_msg.ahk:
; auto-execute section when ran standalone
#SingleInstance force
#Warn
AutoExec() ; we can call this if we want
MyMsg("test our function")
return
; auto-execute section when ran from Python
AutoExec() {
SetBatchLines, 100ms ; slow our code to reduce CPU
}
MyMsg(text) {
MsgBox, % text
}
Settings from AutoExec() will still apply even though we execute from OnMessage()
for speed.
AutoHotkey's #Warn is special and will apply to both standalone and from-Python execution, unless you add/remove it dynamically.
call(proc, ...)
is for performance, to avoid receiving a large unneeded result.
get(var)
set(var, val)
are shorthand for global variables and built-ins like A_TimeIdle
.
f(func, ...)
get(var)
will infer float
and int
(base-16 beginning with 0x
) like AutoHotkey.
f_raw(func, ...)
get_raw(var)
will return the raw string as-stored.
call_main(proc, ...)
f_main(func, ...)
f_raw_main(func, ...)
will execute on AutoHotkey's main thread, instead of OnMessage().
This is necessary if AhkCantCallOutInInputSyncCallError
is thrown, generally from some uses of ComObjCreate().
This is slower (except with very large data), but still fast and unlikely to bottleneck.
Example event loop
See bottom of AHK script for hotkeys
import sys
import time
from datetime import datetime
from enum import Enum
from pathlib import Path
from ahkunwrapped import Script, AhkExitException
choice = None
HOTKEY_SEND_CHOICE = 'F2'
class Event(Enum):
QUIT, SEND_CHOICE, CLEAR_CHOICE, CHOOSE_MONTH, CHOOSE_DAY = range(5)
ahk = Script.from_file(Path('example.ahk'), format_dict=globals())
def main() -> None:
ts = 0
while True:
exit_code = ahk.poll()
if exit_code:
sys.exit(exit_code)
try:
s_elapsed = time.time() - ts
if s_elapsed >= 60:
ts = time.time()
print_minute()
event = ahk.get('event')
if event:
ahk.set('event', '')
on_event(event)
except AhkExitException:
print("Graceful exit.")
sys.exit(0)
time.sleep(0.01)
def print_minute() -> None:
print(f'It is now {datetime.now().time()}')
def on_event(event: str) -> None:
global choice
def get_choice() -> str:
return choice or datetime.now().strftime('%#I:%M %p')
if event == str(Event.QUIT):
ahk.exit()
if event == str(Event.CLEAR_CHOICE):
choice = None
if event == str(Event.SEND_CHOICE):
ahk.call('Send', f'{get_choice()} ')
if event == str(Event.CHOOSE_MONTH):
choice = datetime.now().strftime('%b')
ahk.call('ToolTip', f'Month is {get_choice()}, {HOTKEY_SEND_CHOICE} to insert.')
if event == str(Event.CHOOSE_DAY):
choice = datetime.now().strftime('%#d')
ahk.call('ToolTip', f'Day is {get_choice()}, {HOTKEY_SEND_CHOICE} to insert.')
if __name__ == '__main__':
main()
example.ahk:
#SingleInstance, force
#Warn
ToolTip("Standalone script test!")
return
AutoExec() {
global event
event := ""
SendMode, input
}
Send(text) {
Send, % text
}
ToolTip(text, s := 2) {
ToolTip, % text
SetTimer, RemoveToolTip, % s * 1000
}
RemoveToolTip:
SetTimer, RemoveToolTip, off
ToolTip,
event = {{Event.CLEAR_CHOICE}}
return
MouseIsOver(winTitle) {
MouseGetPos,,, winId
result := WinExist(winTitle " ahk_id " winId)
return result
}
#If WinActive("ahk_class Notepad")
{{HOTKEY_SEND_CHOICE}}::event = {{Event.SEND_CHOICE}}
^Q::event = {{Event.QUIT}}
#If MouseIsOver("ahk_class Notepad")
WheelUp::event = {{Event.CHOOSE_MONTH}}
WheelDown::event = {{Event.CHOOSE_DAY}}
Example PyInstaller spec (single .exe)
example.spec:
# -*- mode: python -*-
from pathlib import Path
import ahkunwrapped
a = Analysis(['example.py'], datas=[
(Path(ahkunwrapped.__file__).parent / 'lib', 'lib'),
('example.ahk', '.'),
]
)
pyz = PYZ(a.pure)
exe = EXE(pyz, a.scripts, a.binaries, a.datas, name='my-example', upx=True, console=False)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for ahkunwrapped-2.0.0.post1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 261ca97f82085bdf184818510203d453082513d19e5f6d53482824ca9859f667 |
|
MD5 | 30bed178b4c86ed3031b81b1a85f0a1b |
|
BLAKE2b-256 | 73a35b927aa0d14696911003f5c3590d6f584c3e707c85e5c82276820b7dd9c0 |