Tray menu and tooltips don't appear unless AHK executor is "stimulated"
describe your issue
Sorry for being so obsessed with tray icons 😅
I noticed that unless I am continuously calling some function from an AHK object, tooltips and the tray icon right-click menu do not appear.
Thanks for your consideration.
ahk.version
1.5.2
AutoHotkey version
v1
Code to reproduce the issue
from ahk import AHK
from ahk.directives import NoTrayIcon
from time import sleep
ahk = AHK()
ahk.menu_tray_tooltip('The problematic one')
def show_tooltip():
print('Showing tooltip')
ahk.hide_tooltip()
ahk.show_tooltip('Hello, world!')
ahk.add_hotkey('~t', show_tooltip) # Press T to bring up the tooltip
ahk.start_hotkeys()
while True:
ahk.get_mouse_position() # I can't right click the tray icon unless this line is uncommented
# This framerate of the tooltip fade-in animation is capped by how often the mouse position is checked
sleep(1)
Traceback/Error message
No response
Hmm. Yeah. The issue around the menu tray is a known issue. The AutoHotkey code uses a FileRead operation to receive commands from Python. Unfortunately, the implementation used in AutoHotkey is that FileRead invokes a blocking syscall on the single thread upon which the AutoHotkey interpreter runs, which prevents any pseudo-threaded events from occurring. While FileRead blocks, AHK is not able to respond to any other events.
I've expounded on this problem on this post/comment. Because AutoHotkey lacks any true threading capability, the solution to this problem is not straightforward. It would be ideal if the maintainers of AutoHotkey would implement FileRead in a way that allowed messages to continue to be processed while waiting on IO.
This is also why the functionality for hotkeys requires a separate AutoHotkey process because this issue would otherwise also block hotkey execution.
I think the problem can be solved, but would require replacing the FileRead operations with a non-blocking equivalent, which doesn't exist in AutoHotkey, so short of AutoHotkey fixing this problem, it would have to be developed through something like DLL calls. There may also be some other workarounds that can be implemented, too.
I see. Out of your hands for now I suppose.
Any idea what the lowest impact "stimulus" action might be? Getting the mouse position runs pretty quickly but if there's some no-op command I could run to make the loop run as fast as possible, that would be useful.
Well. Maybe not out of my hands so much as it's been too tall of a hill to climb to fix the blocking aspect of reading commands. But that could change or other workarounds could be found.
As far as the lowest impact stimulus action, I'm not sure, but in your situation, I would consider one of two routes:
- There's an undocumented
AHKEchofunction that can be called which is pretty low overhead. It just returns the string it was sent. I doubt any other operation would be meaningfully faster, but I haven't actually tested speed of it. - You could create a brand new no-op function using the extensions API.
Approach 1 is pretty simple:
# ...
while True:
print('no op')
ahk.function_call('AHKEcho', [''])
time.sleep(1)
Approach 2 would look like this:
from ahk import AHK
from ahk.extensions import Extension
from typing import Literal
import time
script_text = r'''
MyNoOpFunction(args*) {
return FormatNoValueResponse()
}
'''
no_op_extension= Extension(script_text=script_text)
@no_op_extension.register # register the method for the extension
def no_op(ahk: AHK) -> None:
result = ahk.function_call('MyNoOpFunction')
return result
ahk = AHK(extensions=[no_op_extension])
# ...
while True:
print('no op')
ahk.no_op()
time.sleep(1)
But just about any command is going to run so fast that it wouldn't make a difference to user perception (I think). If the goal is lowest impact in terms of CPU usage, you're pretty much going to be in full control of that by how often you call the function.
Perfect. Thanks for your help on this!
Coming back around to this. One possible method to deal with this is to implement a communication method that is nonblocking on the AutoHotkey side.
I had experimented with named pipes in the past as a communication method. Though, it was scrapped for the simplicity of stdin/stdout and avoiding adding pywin32 as a new dependency. Although it wasn't an objective at the time, named pipes would offer the ability to work asynchronously, even in AutoHotkey's single-threaded execution model.
So it could be worthwhile to revisit the possibility of using pipes or other mechanisms that may solve this problem.