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"