diff --git a/src/pimuniqueapplication.cpp b/src/pimuniqueapplication.cpp index 321e5f8..3c4801b 100644 --- a/src/pimuniqueapplication.cpp +++ b/src/pimuniqueapplication.cpp @@ -1,193 +1,211 @@ /* This file is part of the KDE project Copyright 2008 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config-kontactinterface.h" #include "pimuniqueapplication.h" #include "kontactinterface_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KontactInterface; namespace { const char kChromiumFlagsEnv[] = "QTWEBENGINE_CHROMIUM_FLAGS"; const char kDisableInProcessStackTraces[] = "--disable-in-process-stack-traces"; } //@cond PRIVATE class Q_DECL_HIDDEN KontactInterface::PimUniqueApplication::Private { public: Private() : cmdArgs(new QCommandLineParser()) {} ~Private() { delete cmdArgs; } static void disableChromiumCrashHandler() { // Disable Chromium's own crash handler, which overrides DrKonqi. auto flags = qgetenv(kChromiumFlagsEnv); if (!flags.contains(kDisableInProcessStackTraces)) { qputenv(kChromiumFlagsEnv, flags + " " + kDisableInProcessStackTraces); } } QCommandLineParser *const cmdArgs; }; //@endcond PimUniqueApplication::PimUniqueApplication(int &argc, char **argv[]) : QApplication(argc, *argv) , d(new Private()) { } PimUniqueApplication::~PimUniqueApplication() { delete d; } QCommandLineParser *PimUniqueApplication::cmdArgs() const { return d->cmdArgs; } void PimUniqueApplication::setAboutData(KAboutData &aboutData) { KAboutData::setApplicationData(aboutData); aboutData.setupCommandLine(d->cmdArgs); // This object name is used in start(), and also in kontact's UniqueAppHandler. const QString objectName = QLatin1Char('/') + QApplication::applicationName() + QLatin1String("_PimApplication"); QDBusConnection::sessionBus().registerObject( objectName, this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportAdaptors); } -bool PimUniqueApplication::start(const QStringList &arguments, bool unique) +static bool callNewInstance(const QString &appName, const QString &serviceName, const QByteArray &asn_id, const QStringList &arguments) +{ + const QString objectName = QLatin1Char('/') + appName + QLatin1String("_PimApplication"); + QDBusInterface iface(serviceName, + objectName, + QStringLiteral("org.kde.PIMUniqueApplication"), + QDBusConnection::sessionBus()); + if (iface.isValid()) { + QDBusReply reply = iface.call(QStringLiteral("newInstance"), + asn_id, + arguments, + QDir::currentPath()); + if (reply.isValid()) { + return true; + } + } + return false; +} + +int PimUniqueApplication::newInstance() +{ + return newInstance(KStartupInfo::startupId(), QStringList() << QApplication::applicationName(), QDir::currentPath()); +} + + +bool PimUniqueApplication::start(const QStringList &arguments) { const QString appName = QApplication::applicationName(); + // Try talking to /appName_PimApplication in org.kde.appName, // (which could be kontact or the standalone application), - // otherwise fall back to starting a new app + // otherwise the current app being started will register to DBus. const QString serviceName = QLatin1String("org.kde.") + appName; if (QDBusConnection::sessionBus().interface()->isServiceRegistered(serviceName)) { QByteArray new_asn_id; #if KONTACTINTERFACE_HAVE_X11 KStartupInfoId id; if (!KStartupInfo::startupId().isEmpty()) { id.initId(KStartupInfo::startupId()); } else { id = KStartupInfo::currentStartupIdEnv(); } if (!id.isNull()) { new_asn_id = id.id(); } #endif KWindowSystem::allowExternalProcessWindowActivation(); - const QString objectName = QLatin1Char('/') + appName + QLatin1String("_PimApplication"); - qCDebug(KONTACTINTERFACE_LOG) << objectName; - QDBusInterface iface(serviceName, - objectName, - QStringLiteral("org.kde.PIMUniqueApplication"), - QDBusConnection::sessionBus()); - if (iface.isValid()) { - QDBusReply reply = iface.call(QStringLiteral("newInstance"), - new_asn_id, - arguments, - QDir::currentPath()); - if (reply.isValid()) { - return false; // success means that main() can exist now. - } + if (callNewInstance(appName, serviceName, new_asn_id, arguments)) { + return false; // success means that main() can exit now. } } qCDebug(KONTACTINTERFACE_LOG) << "kontact not running -- start standalone application"; - if (unique) { - QDBusConnection::sessionBus().registerService(serviceName); - } + QDBusConnection::sessionBus().registerService(serviceName); // Make sure we have DrKonqi Private::disableChromiumCrashHandler(); static_cast(qApp)->activate(arguments, QDir::currentPath()); return true; } -int PimUniqueApplication::newInstance() +bool PimUniqueApplication::activateApplication(const QString &appName, const QStringList &additionalArguments) { - return newInstance(KStartupInfo::startupId(), QStringList() << QApplication::applicationName(), QDir::currentPath()); + const QString serviceName = QLatin1String("org.kde.") + appName; + QStringList arguments{ appName }; + arguments += additionalArguments; + // Start it standalone if not already running (if kontact is running, then this will do nothing) + QDBusConnection::sessionBus().interface()->startService(serviceName); + return callNewInstance(appName, serviceName, KStartupInfo::createNewStartupId(), arguments); + } // This is called via DBus either by another instance that has just been // started or by Kontact when the module is activated int PimUniqueApplication::newInstance(const QByteArray &startupId, const QStringList &arguments, const QString &workingDirectory) { KStartupInfo::setStartupId(startupId); const QWidgetList tlws = topLevelWidgets(); for (QWidget *win : tlws) { if (qobject_cast(win)) { win->show(); KStartupInfo::setNewStartupId(win, startupId); // this moves 'win' to the current desktop #ifdef Q_OS_WIN KWindowSystem::forceActiveWindow(win->winId()); #endif break; } } activate(arguments, workingDirectory); return 0; } int PimUniqueApplication::activate(const QStringList &arguments, const QString &workingDirectory) { Q_UNUSED(arguments); Q_UNUSED(workingDirectory); return 0; } diff --git a/src/pimuniqueapplication.h b/src/pimuniqueapplication.h index 4deda1a..1744dd2 100644 --- a/src/pimuniqueapplication.h +++ b/src/pimuniqueapplication.h @@ -1,73 +1,84 @@ /* This file is part of the KDE project Copyright 2008 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KONTACTINTERFACE_PIMUNIQUEAPPLICATION_H #define KONTACTINTERFACE_PIMUNIQUEAPPLICATION_H #include "kontactinterface_export.h" #include class KAboutData; class QCommandLineParser; namespace KontactInterface { /** * KDEPIM applications which can be integrated into kontact should use - * PimUniqueApplication instead of Qapplication + Dbus unique. + * PimUniqueApplication instead of QApplication + Dbus unique. * This makes command-line handling work, i.e. you can launch "korganizer" * and if kontact is already running, it will load the korganizer part and * switch to it. */ class KONTACTINTERFACE_EXPORT PimUniqueApplication : public QApplication { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.PIMUniqueApplication") public: explicit PimUniqueApplication(int &argc, char **argv[]); ~PimUniqueApplication(); void setAboutData(KAboutData &aboutData); - static bool start(const QStringList &arguments, - bool unique = true); + + /** + * Register this process as a unique application, if not already running. + * Typically called in main(). + * @param arguments should start with the appname, as QCoreApplication::arguments() does. + */ + static bool start(const QStringList &arguments); + + /** + * Ensure that another PIM application is running. + */ + static bool activateApplication(const QString &application, + const QStringList &additionalArguments = {}); Q_REQUIRED_RESULT QCommandLineParser *cmdArgs() const; public Q_SLOTS: Q_SCRIPTABLE int newInstance(); Q_SCRIPTABLE virtual int newInstance(const QByteArray &startupId, const QStringList &arguments, const QString &workingDirectory); protected: virtual int activate(const QStringList &arguments, const QString &workingDirectory); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } #endif diff --git a/src/uniqueapphandler.h b/src/uniqueapphandler.h index 2c9577b..2077005 100644 --- a/src/uniqueapphandler.h +++ b/src/uniqueapphandler.h @@ -1,141 +1,141 @@ /* This file is part of the KDE Kontact Plugin Interface Library. Copyright (c) 2003,2008 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KONTACTINTERFACE_UNIQUEAPPHANDLER_H #define KONTACTINTERFACE_UNIQUEAPPHANDLER_H #include "kontactinterface_export.h" #include "plugin.h" class QCommandLineParser; namespace KontactInterface { /** * D-Bus Object that has the name of the standalone application (e.g. "kmail") * and implements newInstance() so that running the separate application does * the right thing when kontact is running. * By default this means simply bringing the main window to the front, * but newInstance can be reimplemented. */ class KONTACTINTERFACE_EXPORT UniqueAppHandler : public QObject { Q_OBJECT - // We implement the KUniqueApplication interface + // We implement the PIMUniqueApplication interface Q_CLASSINFO("D-Bus Interface", "org.kde.PIMUniqueApplication") public: UniqueAppHandler(Plugin *plugin); ~UniqueAppHandler() override; /// This must be reimplemented so that app-specific command line options can be parsed virtual void loadCommandLineOptions(QCommandLineParser *parser) = 0; Q_REQUIRED_RESULT Plugin *plugin() const; /** Sets the main QWidget @p widget associated with this application. @param widget the QWidget to set as main */ static void setMainWidget(QWidget *widget); /** Returns the main widget, which will zero if setMainWidget() has not be called yet. @since 4.6 */ Q_REQUIRED_RESULT QWidget *mainWidget(); public Q_SLOTS: // DBUS methods int newInstance(const QByteArray &asn_id, const QStringList &args, const QString &workingDirectory); bool load(); protected: virtual int activate(const QStringList &args, const QString &workingDirectory); private: class Private; Private *const d; }; /// Base class for UniqueAppHandler class UniqueAppHandlerFactoryBase { public: virtual ~UniqueAppHandlerFactoryBase() {} virtual UniqueAppHandler *createHandler(Plugin *) = 0; }; /** * Used by UniqueAppWatcher below, to create the above UniqueAppHandler object * when necessary. * The template argument is the UniqueAppHandler-derived class. * This allows to remove the need to subclass UniqueAppWatcher. */ template class UniqueAppHandlerFactory : public UniqueAppHandlerFactoryBase { public: UniqueAppHandler *createHandler(Plugin *plugin) override { plugin->registerClient(); return new T(plugin); } }; /** * If the standalone application is running by itself, we need to watch * for when the user closes it, and activate the uniqueapphandler then. * This prevents, on purpose, that the standalone app can be restarted. * Kontact takes over from there. * */ class KONTACTINTERFACE_EXPORT UniqueAppWatcher : public QObject { Q_OBJECT public: /** * Create an instance of UniqueAppWatcher, which does everything necessary * for the "unique application" behavior: create the UniqueAppHandler as soon * as possible, i.e. either right now or when the standalone app is closed. * * @param factory templatized factory to create the handler. Example: * ... Note that the watcher takes ownership of the factory. * @param plugin is the plugin application */ UniqueAppWatcher(UniqueAppHandlerFactoryBase *factory, Plugin *plugin); ~UniqueAppWatcher() override; bool isRunningStandalone() const; private Q_SLOTS: void slotApplicationRemoved(const QString &name, const QString &oldOwner, const QString &newOwner); private: class Private; Private *const d; }; } // namespace #endif