KDEUI
kuniqueapplication.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (c) 1999 Preston Brown <pbrown@kde.org> 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License as published by the Free Software Foundation; either 00007 version 2 of the License, or (at your option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "kuniqueapplication.h" 00021 #include "kuniqueapplication_p.h" 00022 #include <kmainwindow.h> 00023 00024 #include <config.h> 00025 00026 #include <sys/types.h> 00027 #include <sys/wait.h> 00028 00029 #include <assert.h> 00030 #include <errno.h> 00031 #include <stdlib.h> 00032 #include <unistd.h> 00033 00034 #include <QtCore/QFile> 00035 #include <QtCore/QList> 00036 #include <QtCore/QTimer> 00037 #include <QtDBus/QtDBus> 00038 00039 #include <kcmdlineargs.h> 00040 #include <kstandarddirs.h> 00041 #include <kaboutdata.h> 00042 #include <kconfiggroup.h> 00043 #include <kwindowsystem.h> 00044 00045 #if defined Q_WS_X11 00046 #include <kstartupinfo.h> 00047 #endif 00048 00049 /* I don't know why, but I end up with complaints about 00050 a forward-declaration of QWidget in the activeWidow()->show 00051 call below on Qt/Mac if I don't include this here... */ 00052 #include <QWidget> 00053 00054 #include <kconfig.h> 00055 #include "kdebug.h" 00056 00057 #if defined Q_WS_X11 00058 #include <netwm.h> 00059 #include <X11/Xlib.h> 00060 #define DISPLAY "DISPLAY" 00061 #else 00062 # ifdef Q_WS_QWS 00063 # define DISPLAY "QWS_DISPLAY" 00064 # else 00065 # define DISPLAY "DISPLAY" 00066 # endif 00067 #endif 00068 00069 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC) 00070 #include <kkernel_mac.h> 00071 #endif 00072 00073 bool KUniqueApplication::Private::s_nofork = false; 00074 bool KUniqueApplication::Private::s_multipleInstances = false; 00075 bool s_kuniqueapplication_startCalled = false; 00076 bool KUniqueApplication::Private::s_handleAutoStarted = false; 00077 #ifdef Q_WS_WIN 00078 /* private helpers from kapplication_win.cpp */ 00079 #ifndef _WIN32_WCE 00080 void KApplication_activateWindowForProcess( const QString& executableName ); 00081 #endif 00082 #endif 00083 00084 void 00085 KUniqueApplication::addCmdLineOptions() 00086 { 00087 KCmdLineOptions kunique_options; 00088 kunique_options.add("nofork", ki18n("Do not run in the background.")); 00089 #ifdef Q_WS_MACX 00090 kunique_options.add("psn", ki18n("Internally added if launched from Finder")); 00091 #endif 00092 KCmdLineArgs::addCmdLineOptions(kunique_options, KLocalizedString(), "kuniqueapp", "kde"); 00093 } 00094 00095 static QDBusConnectionInterface *tryToInitDBusConnection() 00096 { 00097 // Check the D-Bus connection health 00098 QDBusConnectionInterface* dbusService = 0; 00099 if (!QDBusConnection::sessionBus().isConnected() || !(dbusService = QDBusConnection::sessionBus().interface())) 00100 { 00101 kError() << "KUniqueApplication: Cannot find the D-Bus session server: " << QDBusConnection::sessionBus().lastError().message() << endl; 00102 ::exit(255); 00103 } 00104 return dbusService; 00105 } 00106 00107 bool KUniqueApplication::start() 00108 { 00109 return start(0); 00110 } 00111 00112 bool 00113 KUniqueApplication::start(StartFlags flags) 00114 { 00115 if( s_kuniqueapplication_startCalled ) 00116 return true; 00117 s_kuniqueapplication_startCalled = true; 00118 00119 addCmdLineOptions(); // Make sure to add cmd line options 00120 #if defined(Q_WS_WIN) || defined(Q_WS_MACX) 00121 Private::s_nofork = true; 00122 #else 00123 KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kuniqueapp"); 00124 Private::s_nofork = !args->isSet("fork"); 00125 #endif 00126 00127 QString appName = KCmdLineArgs::aboutData()->appName(); 00128 const QStringList parts = KCmdLineArgs::aboutData()->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts); 00129 if (parts.isEmpty()) 00130 appName.prepend(QLatin1String("local.")); 00131 else 00132 foreach (const QString& s, parts) 00133 { 00134 appName.prepend(QLatin1Char('.')); 00135 appName.prepend(s); 00136 } 00137 00138 bool forceNewProcess = Private::s_multipleInstances || flags & NonUniqueInstance; 00139 00140 if (Private::s_nofork) 00141 { 00142 00143 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC) 00144 mac_initialize_dbus(); 00145 #endif 00146 00147 QDBusConnectionInterface* dbusService = tryToInitDBusConnection(); 00148 00149 QString pid = QString::number(getpid()); 00150 if (forceNewProcess) 00151 appName = appName + '-' + pid; 00152 00153 // Check to make sure that we're actually able to register with the D-Bus session 00154 // server. 00155 bool registered = dbusService->registerService(appName) == QDBusConnectionInterface::ServiceRegistered; 00156 if (!registered) 00157 { 00158 kError() << "KUniqueApplication: Can't setup D-Bus service. Probably already running." 00159 << endl; 00160 #if defined(Q_WS_WIN) && !defined(_WIN32_WCE) 00161 KApplication_activateWindowForProcess(KCmdLineArgs::aboutData()->appName()); 00162 #endif 00163 ::exit(255); 00164 } 00165 00166 // We'll call newInstance in the constructor. Do nothing here. 00167 return true; 00168 00169 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC) 00170 } else { 00171 mac_fork_and_reexec_self(); 00172 #endif 00173 00174 } 00175 00176 #ifndef Q_WS_WIN 00177 int fd[2]; 00178 signed char result; 00179 if (0 > pipe(fd)) 00180 { 00181 kError() << "KUniqueApplication: pipe() failed!" << endl; 00182 ::exit(255); 00183 } 00184 int fork_result = fork(); 00185 switch(fork_result) { 00186 case -1: 00187 kError() << "KUniqueApplication: fork() failed!" << endl; 00188 ::exit(255); 00189 break; 00190 case 0: 00191 { 00192 // Child 00193 00194 QDBusConnectionInterface* dbusService = tryToInitDBusConnection(); 00195 ::close(fd[0]); 00196 if (forceNewProcess) 00197 appName.append("-").append(QString::number(getpid())); 00198 00199 QDBusReply<QDBusConnectionInterface::RegisterServiceReply> reply = 00200 dbusService->registerService(appName); 00201 if (!reply.isValid()) 00202 { 00203 kError() << "KUniqueApplication: Can't setup D-Bus service." << endl; 00204 result = -1; 00205 ::write(fd[1], &result, 1); 00206 ::exit(255); 00207 } 00208 if (reply == QDBusConnectionInterface::ServiceNotRegistered) 00209 { 00210 // Already running. Ok. 00211 result = 0; 00212 ::write(fd[1], &result, 1); 00213 ::close(fd[1]); 00214 return false; 00215 } 00216 00217 #ifdef Q_WS_X11 00218 KStartupInfoId id; 00219 if( kapp != NULL ) // KApplication constructor unsets the env. variable 00220 id.initId( kapp->startupId()); 00221 else 00222 id = KStartupInfo::currentStartupIdEnv(); 00223 if( !id.none()) 00224 { // notice about pid change 00225 Display* disp = XOpenDisplay( NULL ); 00226 if( disp != NULL ) // use extra X connection 00227 { 00228 KStartupInfoData data; 00229 data.addPid( getpid()); 00230 KStartupInfo::sendChangeX( disp, id, data ); 00231 XCloseDisplay( disp ); 00232 } 00233 } 00234 #else //FIXME(E): Implement 00235 #endif 00236 } 00237 result = 0; 00238 ::write(fd[1], &result, 1); 00239 ::close(fd[1]); 00240 return true; // Finished. 00241 default: 00242 // Parent 00243 00244 if (forceNewProcess) 00245 appName.append("-").append(QString::number(fork_result)); 00246 ::close(fd[1]); 00247 00248 Q_FOREVER 00249 { 00250 int n = ::read(fd[0], &result, 1); 00251 if (n == 1) break; 00252 if (n == 0) 00253 { 00254 kError() << "KUniqueApplication: Pipe closed unexpectedly." << endl; 00255 ::exit(255); 00256 } 00257 if (errno != EINTR) 00258 { 00259 kError() << "KUniqueApplication: Error reading from pipe." << endl; 00260 ::exit(255); 00261 } 00262 } 00263 ::close(fd[0]); 00264 00265 if (result != 0) 00266 ::exit(result); // Error occurred in child. 00267 00268 #endif 00269 QDBusConnectionInterface* dbusService = tryToInitDBusConnection(); 00270 if (!dbusService->isServiceRegistered(appName)) 00271 { 00272 kError() << "KUniqueApplication: Registering failed!" << endl; 00273 } 00274 00275 QByteArray saved_args; 00276 QDataStream ds(&saved_args, QIODevice::WriteOnly); 00277 KCmdLineArgs::saveAppArgs(ds); 00278 00279 QByteArray new_asn_id; 00280 #if defined Q_WS_X11 00281 KStartupInfoId id; 00282 if( kapp != NULL ) // KApplication constructor unsets the env. variable 00283 id.initId( kapp->startupId()); 00284 else 00285 id = KStartupInfo::currentStartupIdEnv(); 00286 if( !id.none()) 00287 new_asn_id = id.id(); 00288 #endif 00289 00290 QDBusInterface iface(appName, "/MainApplication", "org.kde.KUniqueApplication", QDBusConnection::sessionBus()); 00291 QDBusReply<int> reply; 00292 if (!iface.isValid() || !(reply = iface.call("newInstance", new_asn_id, saved_args)).isValid()) 00293 { 00294 QDBusError err = iface.lastError(); 00295 kError() << "Communication problem with " << KCmdLineArgs::aboutData()->appName() << ", it probably crashed." << endl 00296 << "Error message was: " << err.name() << ": \"" << err.message() << "\"" << endl; 00297 ::exit(255); 00298 } 00299 #ifndef Q_WS_WIN 00300 ::exit(reply); 00301 break; 00302 } 00303 #endif 00304 return false; // make insure++ happy 00305 } 00306 00307 00308 KUniqueApplication::KUniqueApplication(bool GUIenabled, bool configUnique) 00309 : KApplication( GUIenabled, Private::initHack( configUnique )), 00310 d(new Private(this)) 00311 { 00312 d->processingRequest = false; 00313 d->firstInstance = true; 00314 00315 // the sanity checking happened in initHack 00316 new KUniqueApplicationAdaptor(this); 00317 00318 if (Private::s_nofork) 00319 // Can't call newInstance directly from the constructor since it's virtual... 00320 QTimer::singleShot( 0, this, SLOT(_k_newInstanceNoFork()) ); 00321 } 00322 00323 00324 #ifdef Q_WS_X11 00325 KUniqueApplication::KUniqueApplication(Display *display, Qt::HANDLE visual, 00326 Qt::HANDLE colormap, bool configUnique) 00327 : KApplication( display, visual, colormap, Private::initHack( configUnique )), 00328 d(new Private(this)) 00329 { 00330 d->processingRequest = false; 00331 d->firstInstance = true; 00332 00333 // the sanity checking happened in initHack 00334 new KUniqueApplicationAdaptor(this); 00335 00336 if (Private::s_nofork) 00337 // Can't call newInstance directly from the constructor since it's virtual... 00338 QTimer::singleShot( 0, this, SLOT(_k_newInstanceNoFork()) ); 00339 } 00340 #endif 00341 00342 00343 KUniqueApplication::~KUniqueApplication() 00344 { 00345 delete d; 00346 } 00347 00348 // this gets called before even entering QApplication::QApplication() 00349 KComponentData KUniqueApplication::Private::initHack(bool configUnique) 00350 { 00351 KComponentData cData(KCmdLineArgs::aboutData()); 00352 if (configUnique) 00353 { 00354 KConfigGroup cg(cData.config(), "KDE"); 00355 s_multipleInstances = cg.readEntry("MultipleInstances", false); 00356 } 00357 if( !KUniqueApplication::start()) 00358 // Already running 00359 ::exit( 0 ); 00360 return cData; 00361 } 00362 00363 void KUniqueApplication::Private::_k_newInstanceNoFork() 00364 { 00365 s_handleAutoStarted = false; 00366 q->newInstance(); 00367 firstInstance = false; 00368 #if defined Q_WS_X11 00369 // KDE4 remove 00370 // A hack to make startup notification stop for apps which override newInstance() 00371 // and reuse an already existing window there, but use KWindowSystem::activateWindow() 00372 // instead of KStartupInfo::setNewStartupId(). Therefore KWindowSystem::activateWindow() 00373 // for now sets this flag. Automatically ending startup notification always 00374 // would cause problem if the new window would show up with a small delay. 00375 if( s_handleAutoStarted ) 00376 KStartupInfo::handleAutoAppStartedSending(); 00377 #endif 00378 // What to do with the return value ? 00379 } 00380 00381 bool KUniqueApplication::restoringSession() 00382 { 00383 return d->firstInstance && isSessionRestored(); 00384 } 00385 00386 int KUniqueApplication::newInstance() 00387 { 00388 if (!d->firstInstance) { 00389 QList<KMainWindow*> allWindows = KMainWindow::memberList(); 00390 if (!allWindows.isEmpty()) { 00391 // This method is documented to only work for applications 00392 // with only one mainwindow. 00393 KMainWindow* mainWindow = allWindows.first(); 00394 if (mainWindow) { 00395 mainWindow->show(); 00396 #ifdef Q_WS_X11 00397 // This is the line that handles window activation if necessary, 00398 // and what's important, it does it properly. If you reimplement newInstance(), 00399 // and don't call the inherited one, use this (but NOT when newInstance() 00400 // is called for the first time, like here). 00401 KStartupInfo::setNewStartupId(mainWindow, startupId()); 00402 #endif 00403 #ifdef Q_WS_WIN 00404 KWindowSystem::forceActiveWindow( mainWindow->winId() ); 00405 #endif 00406 00407 } 00408 } 00409 } 00410 return 0; // do nothing in default implementation 00411 } 00412 00413 void KUniqueApplication::setHandleAutoStarted() 00414 { 00415 Private::s_handleAutoStarted = false; 00416 } 00417 00419 00420 int KUniqueApplicationAdaptor::newInstance(const QByteArray &asn_id, const QByteArray &args) 00421 { 00422 if (!asn_id.isEmpty()) 00423 parent()->setStartupId(asn_id); 00424 00425 const int index = parent()->metaObject()->indexOfMethod("loadCommandLineOptionsForNewInstance"); 00426 if (index != -1) { 00427 // This hook allows the application to set up KCmdLineArgs using addCmdLineOptions 00428 // before we load the app args. Normally not necessary, but needed by kontact 00429 // since it switches to other sets of options when called as e.g. kmail or korganizer 00430 QMetaObject::invokeMethod(parent(), "loadCommandLineOptionsForNewInstance"); 00431 } 00432 00433 QDataStream ds(args); 00434 KCmdLineArgs::loadAppArgs(ds); 00435 00436 int ret = parent()->newInstance(); 00437 // Must be done out of the newInstance code, in case it is overloaded 00438 parent()->d->firstInstance = false; 00439 return ret; 00440 } 00441 00442 #include "kuniqueapplication.moc" 00443 #include "kuniqueapplication_p.moc"
KDE 4.6 API Reference