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"