File indexing completed on 2024-05-12 05:21:08

0001 /*
0002   This file is part of the KDE Kontact Plugin Interface Library.
0003 
0004   SPDX-FileCopyrightText: 2003, 2008 David Faure <faure@kde.org>
0005 
0006   SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "uniqueapphandler.h"
0010 #include "core.h"
0011 
0012 #include "processes.h"
0013 
0014 #include "kontactinterface_debug.h"
0015 #include <kwindowsystem.h>
0016 
0017 #include <QDBusConnection>
0018 #include <QDBusConnectionInterface>
0019 
0020 #include <QCommandLineParser>
0021 
0022 #include <config-kontactinterface.h>
0023 #if KONTACTINTERFACE_HAVE_X11
0024 #include <KStartupInfo>
0025 #endif
0026 
0027 #ifdef Q_OS_WIN
0028 #include <process.h>
0029 #endif
0030 
0031 /*
0032  Test plan for the various cases of interaction between standalone apps and kontact:
0033 
0034  1) start kontact, select "Mail".
0035  1a) type "korganizer" -> it switches to korganizer
0036  1b) type "kmail" -> it switches to kmail
0037  1c) type "kaddressbook" -> it switches to kaddressbook
0038  1d) type "kmail foo@kde.org" -> it opens a kmail composer, without switching
0039  1e) type "knode" -> it switches to knode [unless configured to be external]
0040  1f) type "kaddressbook --new-contact" -> it opens a kaddressbook contact window
0041 
0042  2) close kontact. Launch kmail. Launch kontact again.
0043  2a) click "Mail" icon -> kontact doesn't load a part, but activates the kmail window
0044  2b) type "kmail foo@kde.org" -> standalone kmail opens composer.
0045  2c) close kmail, click "Mail" icon -> kontact loads the kmail part.
0046  2d) type "kmail" -> kontact is brought to front
0047 
0048  3) close kontact. Launch korganizer, then kontact.
0049  3a) both Todo and Calendar activate the running korganizer.
0050  3b) type "korganizer" -> standalone korganizer is brought to front
0051  3c) close korganizer, click Calendar or Todo -> kontact loads part.
0052  3d) type "korganizer" -> kontact is brought to front
0053 
0054  4) close kontact. Launch kaddressbook, then kontact.
0055  4a) "Contacts" icon activate the running kaddressbook.
0056  4b) type "kaddressbook" -> standalone kaddressbook is brought to front
0057  4c) close kaddressbook, type "kaddressbook -a foo@kde.org" -> kontact loads part and opens editor
0058  4d) type "kaddressbook" -> kontact is brought to front
0059 
0060  5) start "kontact --module summaryplugin"
0061  5a) type "qdbus org.kde.kmail /kmail_PimApplication newInstance '' ''" ->
0062      kontact switches to kmail (#103775)
0063  5b) type "kmail" -> kontact is brought to front
0064  5c) type "kontact" -> kontact is brought to front
0065  5d) type "kontact --module summaryplugin" -> kontact switches to summary
0066 
0067 */
0068 
0069 using namespace KontactInterface;
0070 
0071 //@cond PRIVATE
0072 class UniqueAppHandler::UniqueAppHandlerPrivate
0073 {
0074 public:
0075     Plugin *mPlugin = nullptr;
0076 };
0077 //@endcond
0078 
0079 UniqueAppHandler::UniqueAppHandler(Plugin *plugin)
0080     : QObject(plugin)
0081     , d(new UniqueAppHandlerPrivate)
0082 {
0083     qCDebug(KONTACTINTERFACE_LOG) << "plugin->objectName():" << plugin->objectName();
0084 
0085     d->mPlugin = plugin;
0086     QDBusConnection session = QDBusConnection::sessionBus();
0087     const QString appName = plugin->objectName();
0088     session.registerService(QLatin1StringView("org.kde.") + appName);
0089     const QString objectName = QLatin1Char('/') + appName + QLatin1StringView("_PimApplication");
0090     session.registerObject(objectName, this, QDBusConnection::ExportAllSlots);
0091 }
0092 
0093 UniqueAppHandler::~UniqueAppHandler()
0094 {
0095     QDBusConnection session = QDBusConnection::sessionBus();
0096     const QString appName = parent()->objectName();
0097     session.unregisterService(QLatin1StringView("org.kde.") + appName);
0098 }
0099 
0100 // DBUS call
0101 int UniqueAppHandler::newInstance(const QByteArray &startupId, const QStringList &args, const QString &workingDirectory)
0102 {
0103     if (KWindowSystem::isPlatformX11()) {
0104 #if KONTACTINTERFACE_HAVE_X11
0105         KStartupInfo::setStartupId(startupId);
0106 #endif
0107     } else if (KWindowSystem::isPlatformWayland()) {
0108         KWindowSystem::setCurrentXdgActivationToken(QString::fromUtf8(startupId));
0109     }
0110 
0111     QCommandLineParser parser;
0112     loadCommandLineOptions(&parser); // implemented by plugin
0113     parser.process(args);
0114 
0115     return activate(args, workingDirectory);
0116 }
0117 
0118 static QWidget *s_mainWidget = nullptr;
0119 
0120 // Plugin-specific newInstance implementation, called by above method
0121 int KontactInterface::UniqueAppHandler::activate(const QStringList &args, const QString &workingDirectory)
0122 {
0123     Q_UNUSED(args)
0124     Q_UNUSED(workingDirectory)
0125 
0126     if (s_mainWidget) {
0127         s_mainWidget->show();
0128         KWindowSystem::activateWindow(s_mainWidget->windowHandle());
0129 #if KONTACTINTERFACE_HAVE_X11
0130         KStartupInfo::appStarted();
0131 #endif
0132     }
0133 
0134     // Then ensure the part appears in kontact
0135     d->mPlugin->core()->selectPlugin(d->mPlugin);
0136     return 0;
0137 }
0138 
0139 Plugin *UniqueAppHandler::plugin() const
0140 {
0141     return d->mPlugin;
0142 }
0143 
0144 bool KontactInterface::UniqueAppHandler::load()
0145 {
0146     (void)d->mPlugin->part(); // load the part without bringing it to front
0147     return true;
0148 }
0149 
0150 //@cond PRIVATE
0151 class Q_DECL_HIDDEN UniqueAppWatcher::UniqueAppWatcherPrivate
0152 {
0153 public:
0154     UniqueAppHandlerFactoryBase *mFactory = nullptr;
0155     Plugin *mPlugin = nullptr;
0156     bool mRunningStandalone;
0157 };
0158 //@endcond
0159 
0160 UniqueAppWatcher::UniqueAppWatcher(UniqueAppHandlerFactoryBase *factory, Plugin *plugin)
0161     : QObject(plugin)
0162     , d(new UniqueAppWatcherPrivate)
0163 {
0164     d->mFactory = factory;
0165     d->mPlugin = plugin;
0166 
0167     // The app is running standalone if 1) that name is known to D-Bus
0168     const QString serviceName = QLatin1StringView("org.kde.") + plugin->objectName();
0169     // Needed for wince build
0170 #undef interface
0171     d->mRunningStandalone = QDBusConnection::sessionBus().interface()->isServiceRegistered(serviceName);
0172 #ifdef Q_OS_WIN
0173     if (d->mRunningStandalone) {
0174         QList<int> pids;
0175         getProcessesIdForName(plugin->objectName(), pids);
0176         const int mypid = getpid();
0177         bool processExits = false;
0178         for (int pid : std::as_const(pids)) {
0179             if (mypid != pid) {
0180                 processExits = true;
0181                 break;
0182             }
0183         }
0184         if (!processExits) {
0185             d->mRunningStandalone = false;
0186         }
0187     }
0188 #endif
0189 
0190     QString owner = QDBusConnection::sessionBus().interface()->serviceOwner(serviceName);
0191     if (d->mRunningStandalone && (owner == QDBusConnection::sessionBus().baseService())) {
0192         d->mRunningStandalone = false;
0193     }
0194 
0195     qCDebug(KONTACTINTERFACE_LOG) << " plugin->objectName()=" << plugin->objectName() << " running standalone:" << d->mRunningStandalone;
0196 
0197     if (d->mRunningStandalone) {
0198         QObject::connect(QDBusConnection::sessionBus().interface(),
0199                          &QDBusConnectionInterface::serviceOwnerChanged,
0200                          this,
0201                          &UniqueAppWatcher::slotApplicationRemoved);
0202     } else {
0203         d->mFactory->createHandler(d->mPlugin);
0204     }
0205 }
0206 
0207 UniqueAppWatcher::~UniqueAppWatcher()
0208 {
0209     delete d->mFactory;
0210 }
0211 
0212 bool UniqueAppWatcher::isRunningStandalone() const
0213 {
0214     return d->mRunningStandalone;
0215 }
0216 
0217 void KontactInterface::UniqueAppWatcher::slotApplicationRemoved(const QString &name, const QString &oldOwner, const QString &newOwner)
0218 {
0219     if (oldOwner.isEmpty() || !newOwner.isEmpty()) {
0220         return;
0221     }
0222 
0223     const QString serviceName = QLatin1StringView("org.kde.") + d->mPlugin->objectName();
0224     if (name == serviceName && d->mRunningStandalone) {
0225         d->mFactory->createHandler(d->mPlugin);
0226         d->mRunningStandalone = false;
0227     }
0228 }
0229 
0230 void KontactInterface::UniqueAppHandler::setMainWidget(QWidget *widget)
0231 {
0232     s_mainWidget = widget;
0233 }
0234 
0235 QWidget *KontactInterface::UniqueAppHandler::mainWidget()
0236 {
0237     return s_mainWidget;
0238 }
0239 
0240 #include "moc_uniqueapphandler.cpp"