skelterjohn / go.wde

Windows, drawing and events for Go
217 stars 46 forks source link

`Stop()` on OS X not stopping #54

Closed remko closed 6 years ago

remko commented 6 years ago

I'm running the following on OS X High Sierra:

package main

import (
  "fmt"
  "github.com/skelterjohn/go.wde"
  _ "github.com/skelterjohn/go.wde/cocoa"
  "time"
)

func main() {
  go func() {
    select {
    case <-time.After(2 * time.Second):
      fmt.Printf("Stopping\n")
      wde.Stop()
    }
  }()
  wde.Run()
  fmt.Printf("Run() finished\n")
}

This prints Stopping, but never Run() finished, so it seems that Stop() isn't making Run() return. Is there anything I can do to fix this?

sqweek commented 6 years ago

Hm, that looks right. Are you using the latest version? wde.Stop() used to call [NSApp terminate:nil] which had the effect of exiting the application. That was changed in 5c5bc9fc to [NSApp stop:nil] which sounds like it should do the job:

https://developer.apple.com/documentation/appkit/nsapplication/1428473-stop

If that's not working as advertised I'm not sure what else we can do. I wonder if something has changed for High Sierra?

sqweek commented 6 years ago

Btw I have another barebones repo which I used to research some OSX platform issues, especially those surrounding the main thread and event loops. I don't have a High Sierra machine (or any OSX machine at the moment), but you might find it a useful testbed to understand what's going on.

https://github.com/sqweek/macmain

remko commented 6 years ago

I'm running the latest version. I tried running terminate myself thinking it would solve my problem, but noticed it indeed stops too quickly.

I also tried running [NSApp stop] from the main loop (which sounded like a good idea, since the docs indicate the exit handling code is done after the event is processed), but that also didn't help. In fact, it still processes main loop events after that.

Which version of OS X do you test on? I tried it on 10.12.6, but same problem.

remko commented 6 years ago

FYI, the following .m file also doesn't work, so it doesn't sound Go/WDE related.

#import <Cocoa/Cocoa.h>

int main(int argc, char* argv[]) {
  [[[NSThread alloc] initWithBlock: ^{
    sleep(2);
    dispatch_async(dispatch_get_main_queue(), ^{ 
      NSLog(@"Stop");
      [[NSApplication sharedApplication] stop:nil];
    });
  }] start];
  [[NSApplication sharedApplication] run];
  NSLog(@"Run finished");
  return 0;
}

I also tried adding a delegate with applicationShouldTerminate, but this wasn't called either.

sqweek commented 6 years ago

I tested on osx 10.9. I managed to resurrect my VM and get a variant of your .m running ([NSThread initWithBlock] was only added in 10.12 it seems), and yeah I see the same behaviour. The event loop keeps going after [NSApplication stop] is called.

Well... damn!

sqweek commented 6 years ago

Ah but, wdetest works fine. Once both windows are closed the event loop stops, I see the println("done") in my terminal and the app exits.

The documentation for [NSApplication stop] says it stops the event loop after the next event is processed. I added another dispatch_async to the test .m file to try and trigger that but it didn't help. Maybe it somehow relies on the program spawning a window?

I've also seen odd behaviour on OSX when spawning dialogs via a simple test program that doesn't open any other windows - https://github.com/sqweek/dialog/blob/master/example/simple/main.go

remko commented 6 years ago

I see. Actually, my app (obviously) has a window, so requiring a window isn't an issue.

My app is calling Stop() as a response to some non-WDE event, and that seems to not work. Calling it in response to a WDE event does make it work. Also, calling 'Stop()' (without close), and then triggering a UI event (e.g. mouse enter the window that's still there) suddenly closes the app.

I wonder if you can programmatically trigger an event that goes into the UI event processing loop ...

remko commented 6 years ago

Calling SetSize(0, 0) after Stop() seems to work for example. (Calling it before Stop() also works, but that's probably because it's not called from the main thread, and prone to race conditions)

Not sure how safe it is to do this on other platforms, but it's enough for me as a workaround :)

remko commented 6 years ago

I actually may have a cleaner solution. Will test it tonight and send a PR if it works.

remko commented 6 years ago

See #55 for a fix. It also works for the windowless program posted above.

skelterjohn commented 6 years ago

Thanks, you two, for keeping this project working. It's appreciated.

sqweek commented 6 years ago

Thanks @remko! And you're welcome @skelterjohn - nice to hear from you briefly :)