ahkunwrapped
ahkunwrapped copied to clipboard
Some Tips and Tricks
#Include for imported modules
While a relative directory #Include works when calling from a script, for a module you intend to import from, you'll need to do something like:
; shared.ahk
#Include {{LIB_DIRECTORY}}/jxon.ahk
; ... other ahk commands
# shared_ahk.py
import os
from pathlib import Path
from typing import Dict
from ahkunwrapped import Script
here = Path(__file__).parent
fname = here / "shared.ahk"
# Prepare directory paths for AutoHotkey #Include directive
lib_directory = str(here / "lib")
# Get the current Python Process ID
python_pid = str(os.getpid())
format_dict: Dict[str, str] = {
"LIB_DIRECTORY": lib_directory,
"PYTHON_PID": python_pid,
}
AHK = Script.from_file(fname, format_dict=format_dict)
# now this will work:
from shared_ahk import AHK
Quickly kill the ahk process
I used ahkunwrapped to automate Quickbooks Desktop. This involves mouse clicks, control-clips, sendinput, etc. Sometimes it's good to have a quick "escape-hatch" and let my office worker stop what is happening.
What I did is make an .ahk script that calls the individual python apps as hotkeys:
; launcher.ahk
^!s:: RunWait, %A_ScriptDir%\order_app.pyw
; shared.ahk
AutoExec() {
BlockInput, Mouse
CoordMode, Mouse, Screen
SetDefaultMouseSpeed, 0
; Delays
SetControlDelay, 150
SetMouseDelay, 100
SetWinDelay, 200
global lock
lock := 0
}
!`:: ; Alt + `
global lock
if lock
{
return
}
Process, Close, {{PYTHON_PID}}
return
Then, my employee can click [ALT] + [`~], and it'll immediately kill the python process. We are using the format_dict argument from Script.from_file to pass along the python PID from os to the ahk process. Also, there were times where I DIDN'T want the process to be able to be killed (while a database connection was open), so that's where our lock can be set AHK.set("lock", 1) and reset AHK.set("lock", 0)
MsgBox Creator GUI
AHK MsgBox has a ton of options, and its hard to get right. I found MsgBoxCreatorX that makes it easy to create your own.
So I created a few custom MsgBox's in my shared.ahk:
MsgBoxYesNo(msgText) {
MsgBox, 262180, Question, %msgText%
ifMsgBox Yes
return "Yes"
else
return "No"
}
And then wrapped them in a Popup class, calling them answer = Popup.yn("Are you sure you want to continue")
from .shared_ahk import AHK
class Popup:
@staticmethod
def ok(msg_text):
return AHK.f("MsgBoxOK", msg_text)
@staticmethod
def get_input(msg_text):
return AHK.f("InputBoxPrompt", msg_text)
@staticmethod
def yn(msg_text):
return AHK.f("MsgBoxYesNo", msg_text)
@staticmethod
def error(msg_text):
return AHK.f("MsgBoxError", msg_text)
Blocking, On-Top InputBox
What about InputBox? It's annoying that it doesn't have Block / Always-on-Top builtin. I've posted that here: InputBoxPrompt
Thought I'd share a couple things I used along the way. With ahkunwrapped I was able to unravel the ahk spaghetti-code I had built up over the years into a well-organized (mostly python!) module for my work. Awesome library.
I'm glad you're having fun!
Killing the parent Python process from AutoHotkey is fine, and that will terminate the child AutoHotkey process(es) because of the Python process job object that prevents orphans.
The main reason to ExitApp from (each) AutoHotkey script is that it won't leave behind a "ghost" icon in the system tray that comes from AutoHotkey being forcibly killed. Not really a problem if your scripts use #NoTrayIcon though.
When the Python code calls ahk.get(), ahk.f(), literally ahk.anything_here()—use ahk.poll() if nothing else—and the script has exited, it will throw AhkExitException so you can optionally exit Python as well. (Though if you have other scripts running, exiting Python will forcibly terminate these soon-to-be orphans as before.)
But if the tray icon cleanup isn't too important, doesn't really matter! Can really do whatever you want. 😉
though if you have other scripts running
I should add tracking all running scripts to the examples, even though needing more than one script is a little niche.
(Probably just a scripts: set[Script] = set() and scripts.remove(ahk) when they throw AhkExitException, and when the set is empty, they've all exited.)
Ah! Shows that I only created hotkeys with ahk and never actually used ExitApp.
So instead, I could simply do:
!`:: ExitApp
And then try/except to catch the AhkExitException in my Python app. So now no need to lock around database/file Python code, since it will only raise exception during AHK calls.
Cool! Thanks again.