I asked our resident kdelibs guru, David Faure, about this and he pointed me at KGlobal::ref() and KGlobal::deref(). What this does is hold a reference count for how many objects are wanting the event loop, and each job will increment the count on creation and decrement it on completion (successful or not, of course).
If your app is using KMainWindow, you already get this nice feature for free as KMainWindow uses it internally so that your app never misses a job-on-exit. Clever.
But what if your app doesn't use KMainWindow? Then you may well need to Roll Your Own(tm). Thankfully, as with most Qt/KDE API tricks, it's pretty easy. First, tell KGlobal you are going to be using the reference system for quitting:
KGlobal::setAllowQuit(true);If you have windows that will be shown, you also need to tell the application to not automatically quit when the last window is closed:
qApp->setQuitOnLastWindowClosed(false);This can go into your main() even. Then in one of your long-lived objects, e.g. that pesky non-KMainWindow widget/dialog/window, grab a ref:
KGlobal::ref();Make sure this will only get called once otherwise the reference counting will get screwed up. The constructor or a run-only-from-the-constructor init() method are good choices.
Now in a method of that same class that will be called when application termination should be triggered (let's call it quit()), and which must be called only after all possible jobs may be started (so do your cleanup here as well, perhaps), drop the reference. Here's an example from plasmawallpaperviewer:
void WallpaperWidget::quit()
{
if (m_wallpaper) {
KConfigGroup config = configGroup();
m_wallpaper->save(config);
delete m_wallpaper;
}
KGlobal::deref();
}
So it cleans up (saves and deletes the wallpaper) and then deref()s. The wallpaper plugin in this case might just decide to save something to the cache and remove the old cache contents. Plasma::Wallpaper in libplasma will use a KIO::file_delete to remove the cache contents, and we don't want that to block regularly, so it isn't exec()d but rather allowed to run asynchronously.
When WallpaperWidget::quit() gets called now (it's connected to the KStandardAction for quitting), if there is a pending job the reference count will not drop to zero quite yet. The application will not exit immediately, the job will start its thing, return as soon as it can, drop its reference ... and then we're at a zero ref count and the application exits.
Of course, if there are no pending jobs the reference count will immediately be zero at this point and the application exits.
This is all well and good, but what if you don't have a widget that spans the lifetime of the application? What if you have a KApplication subclass that is the lifetime definer for your application? What if someone calls kquitapp myapp? Uh-oh!
(This happens to be the case with apps like plasma-desktop. *sigh*)
Good news is that QCoreApplication::quit() is a slot ... which means that unless it's called directly like qApp->quit() it's essentially virtual. Anything connecting to it via a signal or using QMetaObject::invokeMethod or some other introspection-based method of calling it will call quit() in your subclass. Neat. Unfortunately this doesn't get around the DBus usage scenario (e.g. kquitapp myapp or a direct qdbus org.kde.myapp /MainApplication quit).
The best way to accomplish this that I've found so far is to declare a
public Q_SLOT: Q_SCRIPTABLE void quit(); in a KApplication subclass used in the application. The DBus interface is generated from scriptable signals, scriptable slots and declared interfaces at runtime so if your quit() slot is Q_SCRIPTABLE that is what will get exported, not KApplication::quit()!This means you can do the set up and initial ref() in your KApplication sublass' constructor, do all your cleanup in its quit() method and finish off with a KGlobal::deref():
void PlasmaApp::quit()
{
cleanup();
KGlobal::deref();
}
Voila! It all works as expected and no jobs are left unstarted just because we're ready to quit.
For probably 99.9% of KDE applications out there, you don't have to worry about this. They probably use KMainWindow and/or have no code paths that can lead to straggler jobs. If you're working on one of those odd 0.1% of apps, though, there is a solution and it's only a handful of lines of code.
Huzzah!

4 comments:
When I reported the issue with insertIntoCache not working I didn't expect it to be such an interesting topic!
There is a typo in the first line of code: KGobal instead of KGlobal.
@bjacob: hehe.. i know! it's amazing what happens when you get a bunch of people banging away on new API ;)
@Benedict: thanks; i've fixed that now.
Hi Aaron,
Although its probably not directly related to this post but I do believe that logout function of KDE needs some love. To be honest, its slow, plain and simple. Sometimes I find myself trying to right-click and choose "Leave ..." just to make sure that indeed I have asked to quit!
While startup is much faster now, why is logout so slow? And yes, I always quit all running apps before logging out.
Post a Comment