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

Tray menu and tooltips don't appear unless AHK executor is "stimulated" #274

Open
KyleTheScientist opened this issue Mar 20, 2024 · 5 comments

Comments

@KyleTheScientist
Copy link

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

@spyoungtech
Copy link
Owner

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.

@KyleTheScientist
Copy link
Author

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.

@spyoungtech
Copy link
Owner

spyoungtech commented Mar 20, 2024

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:

  1. There's an undocumented AHKEcho function 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.
  2. 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.

@KyleTheScientist
Copy link
Author

Perfect. Thanks for your help on this!

@spyoungtech
Copy link
Owner

spyoungtech commented Mar 29, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants