fyne-io / systray

a cross platfrom Go library to place an icon and menu in the notification area
Apache License 2.0
223 stars 41 forks source link

Windows 11 & RunWithExternalLoop doesn't show menu #67

Closed dirslashls closed 8 months ago

dirslashls commented 8 months ago

Hello, I have systray integrated with gioui and they work together great on Mac. However, on Windows 11 the app icon is present in the tray but the menu items added are not showing up.

Works:

systray.Run(onReady,func() {})

Not working:

func main() {
    // systray.Run(onReady,func() {})
    start, end := systray.RunWithExternalLoop(onReady, func() {})
    start()
    app.Main()
    end()
    os.Exit(0)
}

Any suggestion on how to get this working?

andydotxyz commented 8 months ago

It cannot be replicated with your code as you provide no setup to demonstrate that the icon appears...

This function certainly does work as it's required by Fyne toolkit as well. If you can provide an actual replication code we can look into it further.

dirslashls commented 8 months ago

Thank you @andydotxyz for quick response. I have created a standalone test case at https://github.com/dirslashls/windows11-systray-gioui

The simple program can be invoked in external and normal mode. When invoked in external mode, the menu is not being displayed for me in Windows 11.

dirslashls commented 8 months ago

I know why RunWithExternalLoop doesn't work on Windows although I don't know how to fix it. The problem is that the GUI thread, which is the main thread, is different from the go routine thread eventually called via RunWithExternalLoop. The GetMessageW API on which this routine waits for events is using a different context. I tried to see if it is possible to force a go routine to a specific OS thread. Unfortunately, that API only makes it sticky into the current thread it is already running on.

I restructured my code such that I only use systray.Run command. It so happens that on Windows and Linux, the app.Main of GUI are practically noops (with just select {}) and so there is no real benefit of using RunWithExternalLoop and then blocking with app.Main. If ever GioUI also wants to block the main thread, then we will have issues integrating both GioUI and systray.

Thanks @andydotxyz for all your support. I am closing this now and will also be removing the above mentioned test case repo I created.

andydotxyz commented 8 months ago

I know why RunWithExternalLoop doesn't work on Windows although I don't know how to fix it. The problem is that the GUI thread, which is the main thread, is different from the go routine thread eventually called via RunWithExternalLoop. The GetMessageW API on which this routine waits for events is using a different context. I tried to see if it is possible to force a go routine to a specific OS thread. Unfortunately, that API only makes it sticky into the current thread it is already running on.

I restructured my code such that I only use systray.Run command. It so happens that on Windows and Linux, the app.Main of GUI are practically noops (with just select {}) and so there is no real benefit of using RunWithExternalLoop and then blocking with app.Main. If ever GioUI also wants to block the main thread, then we will have issues integrating both GioUI and systray.

Thanks @andydotxyz for all your support. I am closing this now and will also be removing the above mentioned test case repo I created.

Ok thanks for this. What was spawning a new goroutines? The runloop is expected to be running on main because most OS require that...

dirslashls commented 8 months ago

See https://github.com/fyne-io/systray/blob/master/systray_windows.go#L919

andydotxyz commented 8 months ago

I restructured my code such that I only use systray.Run command. It so happens that on Windows and Linux, the app.Main of GUI are practically noops (with just select {}) and so there is no real benefit of using RunWithExternalLoop and then blocking with app.Main. If ever GioUI also wants to block the main thread, then we will have issues integrating both GioUI and systray.

It certainly should work. Fyne uses RunWithExternalLoop and it works correctly on all platforms. Are you certain there were no other goroutines created with the GioUI integration in your code?

andydotxyz commented 8 months ago

I tried to see if it is possible to force a go routine to a specific OS thread. Unfortunately, that API only makes it sticky into the current thread it is already running on.

Would it help to lock to the thread before invoking any systray code?

dirslashls commented 8 months ago

As far as I know you can't schedule a go routine to a specific thread.

I used go lang windows api to print the thread id and so with RunWithExternalLoop it always ended up in a separate thread. What is surprising is that using "go systray.Run" which is equivalent, ended up on the same thread (perhaps most of the time as even that didn't work a couple of times). I don't think we have any guarantees from go runtime on which go routine is scheduled on what thread.

andydotxyz commented 8 months ago

There are guarantees in https://pkg.go.dev/runtime#LockOSThread

dirslashls commented 8 months ago

Yes, you can call it from within the go routine to make it sticky to the thread it is already assigned. How do you start it on a specific thread, like the main thread?

andydotxyz commented 8 months ago

By calling that in your init function. I think that is in their docs.

dirslashls commented 8 months ago

init runs within main I think. But go routine that is spawned from main doesn't inherit the thread due to LockOSThread. I think there is an open issue in golang asking to let the child go routines inherit the parent context.

andydotxyz commented 8 months ago

I guess I'm out of ideas sorry. I know this works but can't figure out why it's not working for you.

dirslashls commented 8 months ago

No worries, I changed my logic to use Run for Windows. It works on Mac, Windows and Linux. BTW, it is a completely free and fully functional desktop app with no expiration if you want to try it. It can be downloaded at https://sqlframes.com/docs-sheets-bi/sheets-bi/desktop/installation/ . I am waiting on getting a DUNS number for Apple developer team account so I can sign the app. For now, it is unsigned.