ahkunwrapped icon indicating copy to clipboard operation
ahkunwrapped copied to clipboard

Some Tips and Tricks

Open ssweber opened this issue 2 years ago • 3 comments

#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.

image

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

ssweber avatar Sep 29 '23 17:09 ssweber

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.

ssweber avatar Sep 29 '23 17:09 ssweber

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.)

CodeOptimist avatar Sep 29 '23 17:09 CodeOptimist

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.

ssweber avatar Sep 29 '23 21:09 ssweber