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

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:
https://wiki.qt.io/Application_Start-up_Patterns
https://developer.apple.com/documentation/appkit/nsapplication
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:
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.

Related

WCSessionDelegate: sessionDidBecomeInactive and sessionDidDeactivate have been marked unavailable, but are required

I just converted a Swift 2 app to Swift 3, using the convert function of Xcode 8.
My code has a class marked as WCSessionDelegate.
In Swift 2 it compiled without the methods sessionDidBecomeInactive and sessionDidDeactivate.
If I compile the Swift 3 version, the compiler complains that my class does not conform to protocol WCSessionDelegate, which is apparently correct.
It then offers to insert stubs for both functions:
public func sessionDidBecomeInactive(_ session: WCSession) { }
public func sessionDidDeactivate(_ session: WCSession) { }
After these stubs are inserted, these errors are reported:
Cannot override 'sessionDidBecomeInactive' which has been marked unavailable
Cannot override 'sessionDidDeactivate' which has been marked unavailable
How can I fix this problem?
Because the delegate methods sessionDidDeactivate and sessionDidBecomeInactive are marked as unavailable on watchOS you will have to make the compiler ignore those pieces of code in the shared class. You can do so using the following preprocessor macro:
#if os(iOS)
public func sessionDidBecomeInactive(_ session: WCSession) { }
public func sessionDidDeactivate(_ session: WCSession) {
session.activate()
}
#endif
Please also note I added the activate call in the sessionDidDeactivate call. This is to re-activate the session on the phone when the user has switched from one paired watch to second paired one. Calling it like this assumes that you have no other threads/part of your code that needs to be given time before the switch occurs. For more information on supporting the quick watch switching you should take a look at the Apple sample code

"strict" mode for QML?

Does Qt's QML language provide any kind of "strict" mode? In particular, there are two features I'd like:
Application crash on reference to undefined or null (e.g. foo = bar when foo is an existing property but bar is not defined, and foo.bar when foo is null)
"hard" asserts (the console.assert feature does not crash the application).
1. Use qml lint
Run qmllint on all .qml and .js files in your build setup
find ./myproject -type f -regex ".*\.\(qml\|js\)" -exec "$QT_DIR/bin/qmllint" \{\} +
2. Crash app on QML error/warning
Write a custom QDebug message handler function static void handler(QtMsgType type, const QMessageLogContext& context, const QString &message); you register via qInstallMessageHandler(&MyQDebugMessageHandler::handler); that turns QML warnings into fatal logs:
if (type == QtWarningMsg)
{
auto fatalWarnings = std::vector<QString>{
QStringLiteral("ReferenceError:"),
QStringLiteral("is not a type"),
QStringLiteral("File not found"),
};
for (const auto &s : fatalWarnings)
{
if (message.contains(s))
{
type = QtFatalMsg;
break;
}
}
}
Then make sure that QDebug messages of type QtFatalMsg crash the app.
3. Crash on console.assert()
console.assert() creates errors but nothing specific to detect them. So adapt point 2. to crash the app on errors as well.

meteor-testing tutorial fails

I started the meteor-testing tutorial, but the 2nd automatic generated test fails with:
TypeError: Cannot call method 'url' of undefined
So it seems that the client variable is not defined. Did anybody experience similar issues? (btw is there a way to debug this)
i'm using ubuntu 14.04 with
Meteor 1.2.0.2
node v4.0.0
xolvio:cucumber 0.19.4_1 CucumberJS for Velocity
Update:
Generated test code intests/cucumber/features/step_definitions/sample_steps.js:
// You can include npm dependencies for support files in tests/cucumber/package.json
var _ = require('underscore');
module.exports = function () {
// You can use normal require here, cucumber is NOT run in a Meteor context (by design)
var url = require('url');
// 1st TEST OK
this.Given(/^I am a new user$/, function () {
server.call('reset'); // server is a connection to the mirror
});
// 2nd TEST FAIL
this.When(/^I navigate to "([^"]*)"$/, function (relativePath) {
// process.env.ROOT_URL always points to the mirror
client.url(url.resolve(process.env.ROOT_URL, relativePath));
});
...
};
I was said to file an issue in the chimp repository, where I was pointed to the solution:
// 2nd TEST FAIL
this.When(/^I navigate to "([^"]*)"$/, function (relativePath) {
// REPLACE client with browser
browser.url(url.resolve(process.env.ROOT_URL, relativePath));
});
This is a short fix, but I'm not sure whether you should later rather use client (seems to be wrapper for different environments).
**Update: ** meanwhile this was fixed, no adaption necessary anymore

Using QNetworkAccessManager across dll

I have a Qt5 application which uses QNetworkAccessManager for network requests which is accessible via a singleton and QPluginLoader to load extensions which add the functionality to the program. Currently I'm using static linking for plugins and everything works just fine.
However I want to switch to using dynamic libraries to separate the core functionality from other parts of the app. I've added the necessary declspec's via macro, and made necessary adjustments in my .pro files.
The problem is that very often (like, 3 of 4 starts) QNetworkAccessManager when used from dlls just returns an empty request or a null pointer. No data, no error string, no headers.
This is the code I'm using for loading plugins:
template <typename PluginType>
static QList<PluginType*> loadModules() {
QList<PluginType*> loadedModules;
foreach (QObject* instance, QPluginLoader::staticInstances()) {
PluginType* plugin = qobject_cast<PluginType*>(instance);
if (plugin) {
loadedModules << plugin;
}
}
QDir modulesDir(qApp->applicationDirPath() + "/modules");
foreach (QString fileName, modulesDir.entryList(QDir::Files)) {
QPluginLoader loader(modulesDir.absoluteFilePath(fileName));
QObject *instance = loader.instance();
PluginType* plugin = qobject_cast<PluginType*>(instance);
if (plugin) {
loadedModules << plugin;
}
}
return loadedModules;
}
Which is used in this non-static non-template overload called during the startup:
bool AppController::loadModules() {
m_window = new AppWindow();
/* some unimportant connection and splashscreen updating */
QList <ModuleInterface*> loadedModules = loadModules<ModuleInterface>();
foreach (ModuleInterface* module, loadedModules) {
m_splash->showMessage(tr("Initializing module: %1").arg(module->getModuleName()),
Qt::AlignBottom | Qt::AlignRight, Qt::white);
module->preinit();
QApplication::processEvents();
// [1]
ControllerInterface *controller = module->getMainController();
m_window->addModule(module->getModuleName(),
QIcon(module->getIconPath()),
controller->primaryWidget(),
controller->settingsWidget());
m_moduleControllers << controller;
}
m_window->addGeneralSettings((new GeneralSettingsController(m_window))->settingsWidget());
m_window->enableSettings();
/* restoring window geometry & showing it */
return true;
}
However, if I insert QThread::sleep(1); into the line marked 1, it works okay, but the loading slows down and I highly doubt it is a stable solution that will work everywhere.
Also, the site I'm sending requests to is MyAnimeList.
All right, now I have finally debugged it. Turned out I deleted internal QNetworkAccessManager in one of the classes that needed unsync access. That, and updating to Qt5.3 seem to have solved my problem.

How to close already open browser from application in Qt

I am using QDeskTopServices to open a URL in my application in Qt, but if the browser is already open in background, it does not come to the foreground and does nothing on calling on QDeskTopServices.
Is there any way to check and close the browser if it is already open in background?
I found an answer for bringing browser to front but still work needed to pass the Url to browser.
#if defined(Q_WS_S60)
TPtrC16 textPtr(reinterpret_cast<const TUint16*>(theUrl.utf16()));
HBufC *param = HBufC::NewMaxLC(textPtr.Length());
param->Des().Copy(_L("4 http://google.com"));
RApaLsSession apaLsSession;
const TUid KBrowserUid = {0x10008D39};
TApaTaskList taskList(CEikonEnv::Static()->WsSession());
TApaTask task = taskList.FindApp(KBrowserUid);
if (task.Exists()){
// Switch to existing browser instance
task.BringToForeground();
HBufC8* param8 = HBufC8::NewLC(param->Length());
param8->Des().Append(*param);
task.SendMessage(TUid::Uid(0), *param8); // UID not used
CleanupStack::PopAndDestroy(param8);
}
else {
if(!apaLsSession.Handle()) {
User::LeaveIfError(apaLsSession.Connect());
}
TThreadId thread;
User::LeaveIfError(apaLsSession.StartDocument(*param, KBrowserUid, thread));
apaLsSession.Close();
}
CleanupStack::PopAndDestroy(param);
#else
//QDesktopServices::openUrl(QUrl("http://google.com"));
#endif
If any suggestion then please add it to the answer.
Problem solved, just add "symbian:TARGET.CAPABILITY += SwEvent" in your project.pro file and make signed app. This will solve the problem :)
QDesktopServices::openUrl(QUrl("http://google.com"));
using the above line you can open browser. And also just add "symbian:TARGET.CAPABILITY += SwEvent" in your project.pro file and make signed app.
Refer this LINK

Resources