fyne-io / systray

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

How to use RunWithExternalLoop #25

Closed James-Pickett closed 2 years ago

James-Pickett commented 2 years ago

Hello,

I'm trying to understand how RunWithExternalLoop works. Calling Run works as expected, but I was trying to see if I can not block the main thread using RunWithExternalLoop, but nothing seems to happen. What am I missing here?

Running on: macOS Montery 12.5 Apple M1 Pro

package main

import (
    "bytes"
    "image"
    "image/color"
    "image/png"
    "time"

    "fyne.io/systray"
)

func main() {
    onReady := func() {
        systray.SetIcon(blackBox())
        systray.SetTooltip("test")
    }

    // this works and blocks the main thread
    // systray.Run(onReady, func() {})

    // this does not not show anything in sys tray
    start, _ := systray.RunWithExternalLoop(onReady, func() {})
    start()

    // expect to see a black box in systray while sleeping
    time.Sleep(time.Second * 10)
}

func blackBox() []byte {
    // create an image
    img := image.NewRGBA(image.Rect(0, 0, 32, 32))

    // set all pixels to black
    for y := 0; y < 32; y++ {
        for x := 0; x < 32; x++ {
            img.Set(x, y, color.Black)
        }
    }

    // return image bytes
    buf := new(bytes.Buffer)
    png.Encode(buf, img)
    return buf.Bytes()
}

Thank you for your help!

andydotxyz commented 2 years ago

Blocking the main thread is not a runloop - no events can run or be handled etc. The RunWithExternalLoop is for toolkits that need to control the main thread - if you are not using one then it will have to be simulated somehow. This is not trivial as a graphical app (as all systray apps are) must handle the OS event etc.

You can see how Fyne does it at https://github.com/fyne-io/fyne/blob/638ae24e4bebfc0b6dbaabf8db5807eeabfafc61/internal/driver/glfw/driver_desktop.go#L32 - notice how the start and stop variables are stored to be called when the GUI app is ready, and when it will exit.

James-Pickett commented 2 years ago

Thanks for the response @andydotxyz! So if I am understanding correctly, RunWithExternalLoop should only be used when I have something else running on the main thread that handle the UI loop?

andydotxyz commented 2 years ago

Yes, that is a good way of putting it. Something has to run the mainloop.

gedw99 commented 1 year ago

@James-Pickett GIOUI might work. Am going to try.

i made a issue here: https://github.com/fyne-io/systray/issues/32

andydotxyz commented 1 year ago

It is unavoidable that some GUI toolkit must own/block the main loop. Fyne, Gio, Qt are all the same in this regard - it is simply required by most operating systems that GUI apps function this way.

Previous versions of systray only provided Run which meant that it would block. RunWithExternalLoop allows you to integrate with another toolkit. The reason there is not an example of using this with Fyne is because we encapsulate it - the whole project is hidden behind myApp.(desktop.App).SetSystemTrayMenu() - a much cleaner API than the complexity of managing a system tray menu directly :).