Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to limit the number of times a hotkey calls a Python function? #358

Open
1 task done
dadada-py opened this issue Dec 20, 2024 · 1 comment
Open
1 task done
Labels
question Further information is requested

Comments

@dadada-py
Copy link

Checked the documentation

describe your feature request

I am not very good at speaking English. I use AI to assist in communication.
I really appreciate your AHK project, it has been very helpful to me. Thank you very much for the effort and time you've invested in it.
Recently, I encountered a problem while using this library. When I hold down a hotkey continuously, it keeps calling the Python function associated with that hotkey. Sometimes, under the Win11 system, a prompt pops up after triggering the hotkey, as shown in the following figure.
123 (2)

The simplified code is as follows:

from ahk import AHK
from ahk.directives import NoTrayIcon
directives = [
    NoTrayIcon(apply_to_hotkeys_process=True)
]

def my_callback():
    print('Hello callback!')

exe_path = 'AutoHotkey64.exe'
ahk = AHK(executable_path=f'{exe_path}', version='v2', directives=directives)

ahk.add_hotkey('^n', callback=my_callback)
ahk.start_hotkeys()
ahk.block_forever()

My requirements are as follows:

  1. When I hold down the {Ctrl}+{n} combination, only call the my_callback() function once.
  2. When I release the {Ctrl}+{n} combination and then press it again, only call the my_callback() function once.

In response to my requirements, how should I modify the aforementioned code?

@spyoungtech
Copy link
Owner

spyoungtech commented Jan 15, 2025

Hmmm. There is a subtle difference in how Hotkeys work under this library. Specifically, the hotkey execution in the AHK process just simply causes Python to queue up the callback and finishes almost instantly.

Normally, AutoHotkey stops the same hotkey from running at the same time. But because the implementation here has the callback finish almost instantly, it means the hotkey can be called many times rapidly.

It is often suggested to do something like this in AutoHotkey:

^n::
   ; ...
  KeyWait n  ; holds the hotkey in execution until the key is released

The behavior unfortunately doesn't work as intended if you try to do the same thing in Python because, as far as AutoHotkey is concerned, the hotkey is already done even if the Python callback is still executing.

There may be room for improvement in how the library dispatches hotkey callbacks to more closely emulate the default behavior of AutoHotkey (e.g., dropping dispatches for hotkeys that are still running).

The only workaround I can think of right now is not ideal. But perhaps you can use a global variable to get this behavior:

running = False
state_lock = threading.Lock()

def my_callback():
    global running
    with state_lock:
        if running:
            return
        else:
            running = True
    try:
        print('starting callback')
        ahk.key_wait('n', released=True)
        print('ending callback')
    finally:
        with state_lock:
            running = False

This concept could be made into a generic wrapper:

def single_execution(wrapped_callback):
    execution_lock = threading.Lock()
    _running = False
    @functools.wraps(wrapped_callback)
    def new_callback(*args, **kwargs):
        nonlocal execution_lock, _running
        with execution_lock:
            if _running:
                return
            else:
                _running = True
        try:
            res = wrapped_callback(*args, **kwargs)
        finally:
            with execution_lock:
                _running = False
    return new_callback


@single_execution
def my_callback():
    print('starting callback')
    ahk.key_wait('n', released=True)
    print('ending callback')

This probably won't solve your problem of potentially exceeding the maximum hotkey interval (on my system, this didn't happen, however). There will still be a lot of threads and calls to the callback function, but the function just turns into a no-op while another instance is running.
Right now, the MaxHotkeysPerInterval directive can be used with AHK v1 (this is not supported in this library when using v2, since AHK removed the directive and replaced it with A_MaxHotkeyInterval) -- you may be able to alleviate the problem by reducing your keyboard's key repeat rate in Windows settings.

@spyoungtech spyoungtech added the question Further information is requested label Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants