File indexing completed on 2024-04-21 14:56:00
0001 /* This file is part of the KDE libraries 0002 Copyright (c) 1999 Preston Brown <pbrown@kde.org> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "kuniqueapplication.h" 0021 #include "kuniqueapplication_p.h" 0022 #include <kmainwindow.h> 0023 0024 #include <sys/types.h> 0025 #include <sys/wait.h> 0026 0027 #include <assert.h> 0028 #include <errno.h> 0029 #include <stdlib.h> 0030 #include <unistd.h> 0031 0032 #include <QFile> 0033 #include <QList> 0034 #include <QTimer> 0035 #include <QDBusConnection> 0036 #include <QDBusConnectionInterface> 0037 #include <QDBusError> 0038 #include <QDBusReply> 0039 0040 #include <kcmdlineargs.h> 0041 #include <klocalizedstring.h> 0042 #include <k4aboutdata.h> 0043 #include <kconfiggroup.h> 0044 #include <kwindowsystem.h> 0045 0046 #include <config-kdelibs4support.h> 0047 #if HAVE_X11 0048 #include <kstartupinfo.h> 0049 #include <netwm.h> 0050 #include <X11/Xlib.h> 0051 #include <fixx11h.h> 0052 #endif 0053 0054 /* I don't know why, but I end up with complaints about 0055 a forward-declaration of QWidget in the activeWidow()->show 0056 call below on Qt/Mac if I don't include this here... */ 0057 #include <QWidget> 0058 0059 #include <kconfig.h> 0060 #include "kdebug.h" 0061 0062 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC) 0063 #include <kkernel_mac.h> 0064 #endif 0065 0066 bool KUniqueApplication::Private::s_startOwnInstance = false; 0067 bool KUniqueApplication::Private::s_multipleInstances = false; 0068 #ifdef Q_OS_WIN 0069 /* private helpers from kapplication_win.cpp */ 0070 #ifndef _WIN32_WCE 0071 void KApplication_activateWindowForProcess(const QString &executableName); 0072 #endif 0073 #endif 0074 0075 void 0076 KUniqueApplication::addCmdLineOptions() 0077 { 0078 KCmdLineOptions kunique_options; 0079 kunique_options.add("nofork", ki18n("Do not run in the background.")); 0080 #ifdef Q_OS_MAC 0081 kunique_options.add("psn", ki18n("Internally added if launched from Finder")); 0082 #endif 0083 KCmdLineArgs::addCmdLineOptions(kunique_options, KLocalizedString(), "kuniqueapp", "kde"); 0084 } 0085 0086 static QDBusConnectionInterface *tryToInitDBusConnection() 0087 { 0088 // Check the D-Bus connection health 0089 QDBusConnectionInterface *dbusService = nullptr; 0090 QDBusConnection sessionBus = QDBusConnection::sessionBus(); 0091 if (!sessionBus.isConnected() || !(dbusService = sessionBus.interface())) { 0092 kError() << "KUniqueApplication: Cannot find the D-Bus session server: " << sessionBus.lastError().message() << endl; 0093 ::exit(255); 0094 } 0095 return dbusService; 0096 } 0097 0098 bool KUniqueApplication::start() 0099 { 0100 return start(StartFlags()); 0101 } 0102 0103 bool 0104 KUniqueApplication::start(StartFlags flags) 0105 { 0106 extern bool s_kuniqueapplication_startCalled; 0107 if (s_kuniqueapplication_startCalled) { 0108 return true; 0109 } 0110 s_kuniqueapplication_startCalled = true; 0111 0112 addCmdLineOptions(); // Make sure to add cmd line options 0113 #if defined(Q_OS_WIN) || defined(Q_OS_MACX) 0114 Private::s_startOwnInstance = true; 0115 #else 0116 KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kuniqueapp"); 0117 Private::s_startOwnInstance = !args->isSet("fork"); 0118 #endif 0119 0120 QString appName = KCmdLineArgs::aboutData()->appName(); 0121 const QStringList parts = KCmdLineArgs::aboutData()->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts); 0122 if (parts.isEmpty()) { 0123 appName.prepend(QLatin1String("local.")); 0124 } else 0125 foreach (const QString &s, parts) { 0126 appName.prepend(QLatin1Char('.')); 0127 appName.prepend(s); 0128 } 0129 0130 bool forceNewProcess = Private::s_multipleInstances || flags & NonUniqueInstance; 0131 0132 if (Private::s_startOwnInstance) { 0133 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC) 0134 mac_initialize_dbus(); 0135 #endif 0136 0137 QDBusConnectionInterface *dbusService = tryToInitDBusConnection(); 0138 0139 QString pid = QString::number(getpid()); 0140 if (forceNewProcess) { 0141 appName = appName + '-' + pid; 0142 } 0143 0144 // Check to make sure that we're actually able to register with the D-Bus session 0145 // server. 0146 bool registered = dbusService->registerService(appName) == QDBusConnectionInterface::ServiceRegistered; 0147 if (!registered) { 0148 kError() << "KUniqueApplication: Can't setup D-Bus service. Probably already running." 0149 << endl; 0150 #if defined(Q_OS_WIN) && !defined(_WIN32_WCE) 0151 KApplication_activateWindowForProcess(KCmdLineArgs::aboutData()->appName()); 0152 #endif 0153 ::exit(255); 0154 } 0155 0156 // We'll call newInstance in the constructor. Do nothing here. 0157 return true; 0158 0159 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC) 0160 } else { 0161 mac_fork_and_reexec_self(); 0162 #endif 0163 0164 } 0165 0166 #ifndef Q_OS_WIN 0167 int fd[2]; 0168 signed char result; 0169 // We use result to talk between child and parent. It can be 0170 // 0: App already running please call newInstance on it 0171 // 1: App was not running, child will call newInstance on itself 0172 // -1: Error, Can't start DBus service 0173 if (0 > pipe(fd)) { 0174 kError() << "KUniqueApplication: pipe() failed!" << endl; 0175 ::exit(255); 0176 } 0177 int fork_result = fork(); 0178 switch (fork_result) { 0179 case -1: 0180 kError() << "KUniqueApplication: fork() failed!" << endl; 0181 ::exit(255); 0182 break; 0183 case 0: { 0184 // Child 0185 0186 QDBusConnectionInterface *dbusService = tryToInitDBusConnection(); 0187 ::close(fd[0]); 0188 if (forceNewProcess) { 0189 appName.append("-").append(QString::number(getpid())); 0190 } 0191 0192 QDBusReply<QDBusConnectionInterface::RegisterServiceReply> reply = 0193 dbusService->registerService(appName); 0194 if (!reply.isValid()) { 0195 kError() << "KUniqueApplication: Can't setup D-Bus service." << endl; 0196 result = -1; 0197 ::write(fd[1], &result, 1); 0198 ::exit(255); 0199 } 0200 if (reply == QDBusConnectionInterface::ServiceNotRegistered) { 0201 // Already running. Ok. 0202 result = 0; 0203 ::write(fd[1], &result, 1); 0204 ::close(fd[1]); 0205 return false; 0206 } 0207 0208 #if HAVE_X11 0209 KStartupInfoId id; 0210 if (kapp != nullptr) { // KApplication constructor unsets the env. variable 0211 id.initId(kapp->startupId()); 0212 } else { 0213 id = KStartupInfo::currentStartupIdEnv(); 0214 } 0215 if (!id.isNull()) { 0216 // notice about pid change 0217 int screen = 0; 0218 xcb_connection_t *connection = xcb_connect(nullptr, &screen); 0219 if (connection != nullptr) { // use extra X connection 0220 KStartupInfoData data; 0221 data.addPid(getpid()); 0222 KStartupInfo::sendChangeXcb(connection, screen, id, data); 0223 xcb_disconnect(connection); 0224 } 0225 } 0226 #endif 0227 } 0228 result = 1; 0229 ::write(fd[1], &result, 1); 0230 ::close(fd[1]); 0231 Private::s_startOwnInstance = true; 0232 return true; // Finished. 0233 default: 0234 // Parent 0235 0236 if (forceNewProcess) { 0237 appName.append("-").append(QString::number(fork_result)); 0238 } 0239 ::close(fd[1]); 0240 0241 Q_FOREVER { 0242 int n = ::read(fd[0], &result, 1); 0243 if (n == 1) 0244 { 0245 break; 0246 } 0247 if (n == 0) 0248 { 0249 kError() << "KUniqueApplication: Pipe closed unexpectedly." << endl; 0250 ::exit(255); 0251 } 0252 if (errno != EINTR) 0253 { 0254 kError() << "KUniqueApplication: Error reading from pipe." << endl; 0255 ::exit(255); 0256 } 0257 } 0258 ::close(fd[0]); 0259 0260 if (result != 0) { 0261 // Only -1 is actually an error 0262 ::exit(result == -1 ? -1 : 0); 0263 } 0264 0265 #endif 0266 QDBusConnectionInterface *dbusService = tryToInitDBusConnection(); 0267 if (!dbusService->isServiceRegistered(appName)) { 0268 kError() << "KUniqueApplication: Registering failed!" << endl; 0269 } 0270 0271 QByteArray saved_args; 0272 QDataStream ds(&saved_args, QIODevice::WriteOnly); 0273 KCmdLineArgs::saveAppArgs(ds); 0274 0275 QByteArray new_asn_id; 0276 #if HAVE_X11 0277 KStartupInfoId id; 0278 if (kapp != nullptr) { // KApplication constructor unsets the env. variable 0279 id.initId(kapp->startupId()); 0280 } else { 0281 id = KStartupInfo::currentStartupIdEnv(); 0282 } 0283 if (!id.isNull()) { 0284 new_asn_id = id.id(); 0285 } 0286 #endif 0287 0288 QDBusMessage msg = QDBusMessage::createMethodCall(appName, "/MainApplication", "org.kde.KUniqueApplication", 0289 "newInstance"); 0290 msg << new_asn_id << saved_args; 0291 QDBusReply<int> reply = QDBusConnection::sessionBus().call(msg, QDBus::Block, INT_MAX); 0292 0293 if (!reply.isValid()) { 0294 QDBusError err = reply.error(); 0295 kError() << "Communication problem with " << KCmdLineArgs::aboutData()->appName() << ", it probably crashed." << endl 0296 << "Error message was: " << err.name() << ": \"" << err.message() << "\"" << endl; 0297 ::exit(255); 0298 } 0299 #ifndef Q_OS_WIN 0300 ::exit(reply); 0301 break; 0302 } 0303 #endif 0304 return false; // make insure++ happy 0305 } 0306 0307 KUniqueApplication::KUniqueApplication(bool GUIenabled, bool configUnique) 0308 : KApplication(GUIenabled, Private::initHack(configUnique)), 0309 d(new Private(this)) 0310 { 0311 d->processingRequest = false; 0312 d->firstInstance = true; 0313 0314 // the sanity checking happened in initHack 0315 new KUniqueApplicationAdaptor(this); 0316 0317 if (Private::s_startOwnInstance) 0318 // Can't call newInstance directly from the constructor since it's virtual... 0319 { 0320 QTimer::singleShot(0, this, SLOT(_k_newInstanceNoFork())); 0321 } 0322 } 0323 0324 KUniqueApplication::~KUniqueApplication() 0325 { 0326 delete d; 0327 } 0328 0329 // this gets called before even entering QApplication::QApplication() 0330 KComponentData KUniqueApplication::Private::initHack(bool configUnique) 0331 { 0332 KComponentData cData(KCmdLineArgs::aboutData()); 0333 if (configUnique) { 0334 KConfigGroup cg(cData.config(), "KDE"); 0335 s_multipleInstances = cg.readEntry("MultipleInstances", false); 0336 } 0337 if (!KUniqueApplication::start()) 0338 // Already running 0339 { 0340 ::exit(0); 0341 } 0342 return cData; 0343 } 0344 0345 void KUniqueApplication::Private::_k_newInstanceNoFork() 0346 { 0347 q->newInstance(); 0348 firstInstance = false; 0349 } 0350 0351 bool KUniqueApplication::restoringSession() 0352 { 0353 return d->firstInstance && isSessionRestored(); 0354 } 0355 0356 int KUniqueApplication::newInstance() 0357 { 0358 if (!d->firstInstance) { 0359 QList<KMainWindow *> allWindows = KMainWindow::memberList(); 0360 if (!allWindows.isEmpty()) { 0361 // This method is documented to only work for applications 0362 // with only one mainwindow. 0363 KMainWindow *mainWindow = allWindows.first(); 0364 if (mainWindow) { 0365 mainWindow->show(); 0366 #if HAVE_X11 0367 mainWindow->setAttribute(Qt::WA_NativeWindow, true); 0368 // This is the line that handles window activation if necessary, 0369 // and what's important, it does it properly. If you reimplement newInstance(), 0370 // and don't call the inherited one, use this (but NOT when newInstance() 0371 // is called for the first time, like here). 0372 KStartupInfo::setNewStartupId(mainWindow->windowHandle(), startupId()); 0373 #endif 0374 #ifdef Q_OS_WIN 0375 KWindowSystem::forceActiveWindow(mainWindow->winId()); 0376 #endif 0377 0378 } 0379 } 0380 } 0381 return 0; // do nothing in default implementation 0382 } 0383 0384 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED 0385 void KUniqueApplication::setHandleAutoStarted() 0386 { 0387 } 0388 #endif 0389 0390 //// 0391 0392 int KUniqueApplicationAdaptor::newInstance(const QByteArray &asn_id, const QByteArray &args) 0393 { 0394 if (!asn_id.isEmpty()) { 0395 parent()->setStartupId(asn_id); 0396 } 0397 0398 const int index = parent()->metaObject()->indexOfMethod("loadCommandLineOptionsForNewInstance"); 0399 if (index != -1) { 0400 // This hook allows the application to set up KCmdLineArgs using addCmdLineOptions 0401 // before we load the app args. Normally not necessary, but needed by kontact 0402 // since it switches to other sets of options when called as e.g. kmail or korganizer 0403 QMetaObject::invokeMethod(parent(), "loadCommandLineOptionsForNewInstance"); 0404 } 0405 0406 QDataStream ds(args); 0407 KCmdLineArgs::loadAppArgs(ds); 0408 0409 int ret = parent()->newInstance(); 0410 // Must be done out of the newInstance code, in case it is overloaded 0411 parent()->d->firstInstance = false; 0412 return ret; 0413 } 0414 0415 #include "moc_kuniqueapplication.cpp" 0416 #include "moc_kuniqueapplication_p.cpp"