简体   繁体   中英

How to start Go's main function from within the NSApplication event loop?

I'm trying to add Sparkle into my Qt ( binding for Go ) app to make it can be updated automatically.

Problem: there is no popup dialog when running the latest version

Here's the code: https://github.com/sparkle-project/Sparkle/blob/master/Sparkle/SUUIBasedUpdateDriver.m#L104

The reason as the author pointed out is NSAlert needs a run loop to work.

I found some docs:

So, as I understand, we have to instantiate NSApplication before creating a QApplication .

void NSApplicationMain(int argc, char *argv[]) {
    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"myMain" owner:NSApp];
    [NSApp run];
}

My Go's main function is something like this:

func main() {
    widgets.NewQApplication(len(os.Args), os.Args)

    ...
    action := widgets.NewQMenuBar(nil).AddMenu2("").AddAction("Check for Updates...")
    // http://doc.qt.io/qt-5/qaction.html#MenuRole-enum
    action.SetMenuRole(widgets.QAction__ApplicationSpecificRole)
    action.ConnectTriggered(func(bool) { sparkle_checkUpdates() })
    ...

    widgets.QApplication_Exec()
}

Question: how can I start Go's main function from within the NSApplicationMain event loop?

Using QApplication together with a Runloop

Regarding your question how to use your QApplication together with a NSRunloop: you are doing it already. Since you are using QApplication (and not QCoreApplication) you already have a Runloop running,

see http://code.qt.io/cgit/qt/qt.git/plain/src/gui/kernel/qeventdispatcher_mac.mm and http://code.qt.io/cgit/qt/qt.git/plain/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm

Proof

A NSTimer needs a run loop to work. So we could add quick test with an existing example Qt app called 'widget' from the repository you referenced in your question.

Adding a small Objective-C test class TimerRunloopTest with a C function wrapper that can be called from GO:

#import <Foundation/Foundation.h>
#include <os/log.h>


@interface TimerRunloopTest : NSObject

- (void)run;

@end

void runTimerRunloopTest() {

    [[TimerRunloopTest new] run];

}


@implementation TimerRunloopTest

- (void)run {

    os_log_t log = os_log_create("widget.example", "RunloopTest");
    os_log(log, "setup happening at %f", NSDate.timeIntervalSinceReferenceDate);


    [NSTimer scheduledTimerWithTimeInterval:1.0
                                     target:self
                                   selector:@selector(timerTick:)
                                   userInfo:nil
                                    repeats:YES];
}

- (void)timerTick:(NSTimer *)timer {
    os_log_t log = os_log_create("widget.example", "RunloopTest");
    os_log(log, "timer tick %f", NSDate.timeIntervalSinceReferenceDate);
}

@end

GO counterpart timerrunlooptest.go

package main

/*
#cgo LDFLAGS: -framework Foundation

void runTimerRunloopTest();
*/
import "C"

func runTimerRunloopTest() { C.runTimerRunloopTest() }

Change in main.go

At the end before app.Exec() add this line:

runTimerRunloopTest()

Build and Run it

Switch loggin on for our logging messages:

sudo log config --subsystem widget.example --mode level:debug

Afterwards build an run it:

$(go env GOPATH)/bin/qtdeploy test desktop examples/basic/widgets

Test

In the macOS Console uitlity we can now see, that the timer ticks are shown, proofing that a run-loop is running

NSAlert

Then you cited in your question, that NSAlert needs a run loop to work. We already proofed that we have one, but testing it explicitely makes sense.

So we can modify timerrunlooptest.go to inform it, that we want to link agains Cocoa also, not only Foundation:

package main

/*
#cgo LDFLAGS: -framework Foundation
#cgo LDFLAGS: -framework Cocoa

void runTimerRunloopTest();
*/
import "C"

func runTimerRunloopTest() { C.runTimerRunloopTest() }

Then we could add the following code to the run method of TimerRunLoopTest:

#import <Cocoa/Cocoa.h>

...


NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"Message";
alert.informativeText = @"Info";
[alert addButtonWithTitle:@"OK"];
[alert runModal];

Result

After doing a

$(go env GOPATH)/bin/qtdeploy test desktop examples/basic/widgets

the native Alert is shown from the GO/QT application as expected:

来自GO / QT的本机警报

Mixing Qt with Native Code

Although we seem to be able to display native alerts in the way described above, there is this hint in the QT documents that may or may not be useful:

Qt's event dispatcher is more flexible than what Cocoa offers, and lets the user spin the event dispatcher (and running QEventLoop::exec) without having to think about whether or not modal dialogs are showing on screen (which is a difference compared to Cocoa). Therefore, we need to do extra management in Qt to handle this correctly, which unfortunately makes mixing native panels hard. The best way at the moment to do this, is to follow the pattern below, where we post the call to the function with native code rather than calling it directly. Then we know that Qt has cleanly updated any pending event loop recursions before the native panel is shown.

see https://doc.qt.io/qt-5/macos-issues.html#using-native-cocoa-panels

There is also a small code example for this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM