diff --git a/src/kdbusservice.cpp b/src/kdbusservice.cpp index d31c540..c37a2de 100644 --- a/src/kdbusservice.cpp +++ b/src/kdbusservice.cpp @@ -1,229 +1,234 @@ /* This file is part of libkdbusaddons Copyright (c) 2011 David Faure Copyright (c) 2011 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kdbusservice.h" #include #include #include #include #include #include "KDBusServiceIface.h" #include "FreeDesktopApplpicationIface.h" #include "config-kdbusaddons.h" #if HAVE_X11 #include #endif #include "kdbusservice_adaptor.h" #include "kdbusserviceextensions_adaptor.h" class KDBusServicePrivate { public: KDBusServicePrivate() : registered(false), exitValue(0) {} QString generateServiceName() { const QCoreApplication *app = QCoreApplication::instance(); const QString domain = app->organizationDomain(); const QStringList parts = domain.split(QLatin1Char('.'), QString::SkipEmptyParts); QString reversedDomain; if (parts.isEmpty()) { reversedDomain = QStringLiteral("local."); } else { Q_FOREACH (const QString &part, parts) { reversedDomain.prepend(QLatin1Char('.')); reversedDomain.prepend(part); } } return reversedDomain + app->applicationName(); } bool registered; QString serviceName; QString errorMessage; int exitValue; }; KDBusService::KDBusService(StartupOptions options, QObject *parent) : QObject(parent), d(new KDBusServicePrivate) { new KDBusServiceAdaptor(this); new KDBusServiceExtensionsAdaptor(this); QDBusConnectionInterface *bus = nullptr; if (!QDBusConnection::sessionBus().isConnected() || !(bus = QDBusConnection::sessionBus().interface())) { d->errorMessage = QLatin1String("Session bus not found\n" "To circumvent this problem try the following command (with Linux and bash)\n" "export $(dbus-launch)"); } if (bus) { d->serviceName = d->generateServiceName(); QString objectPath = QLatin1Char('/') + d->serviceName; objectPath.replace(QLatin1Char('.'), QLatin1Char('/')); objectPath.replace(QLatin1Char('-'), QLatin1Char('_')); // see spec change at https://bugs.freedesktop.org/show_bug.cgi?id=95129 if (options & Multiple) { const QString pid = QString::number(QCoreApplication::applicationPid()); d->serviceName += QLatin1Char('-') + pid; } QDBusConnection::sessionBus().registerObject(QStringLiteral("/MainApplication"), QCoreApplication::instance(), QDBusConnection::ExportAllSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportAdaptors); QDBusConnection::sessionBus().registerObject(objectPath, this, QDBusConnection::ExportAdaptors); d->registered = bus->registerService(d->serviceName) == QDBusConnectionInterface::ServiceRegistered; if (!d->registered) { if (options & Unique) { // Already running so it's ok! QVariantMap platform_data; platform_data.insert(QStringLiteral("desktop-startup-id"), QString::fromUtf8(qgetenv("DESKTOP_STARTUP_ID"))); if (QCoreApplication::arguments().count() > 1) { OrgKdeKDBusServiceInterface iface(d->serviceName, objectPath, QDBusConnection::sessionBus()); iface.setTimeout(5 * 60 * 1000); // Application can take time to answer QDBusReply reply = iface.CommandLine(QCoreApplication::arguments(), QDir::currentPath(), platform_data); if (reply.isValid()) { exit(reply.value()); } else { d->errorMessage = reply.error().message(); } } else { OrgFreedesktopApplicationInterface iface(d->serviceName, objectPath, QDBusConnection::sessionBus()); iface.setTimeout(5 * 60 * 1000); // Application can take time to answer QDBusReply reply = iface.Activate(platform_data); if (reply.isValid()) { exit(0); } else { d->errorMessage = reply.error().message(); } } } else { d->errorMessage = QLatin1String("Couldn't register name '") + d->serviceName + QLatin1String("' with DBUS - another process owns it already!"); } } else { if (QCoreApplication *app = QCoreApplication::instance()) { connect(app, SIGNAL(aboutToQuit()), this, SLOT(unregister())); } } } if (!d->registered && ((options & NoExitOnFailure) == 0)) { qCritical() << d->errorMessage; exit(1); } } KDBusService::~KDBusService() { delete d; } bool KDBusService::isRegistered() const { return d->registered; } QString KDBusService::errorMessage() const { return d->errorMessage; } void KDBusService::setExitValue(int value) { d->exitValue = value; } +QString KDBusService::serviceName() const +{ + return d->serviceName; +} + void KDBusService::unregister() { QDBusConnectionInterface *bus = nullptr; if (!d->registered || !QDBusConnection::sessionBus().isConnected() || !(bus = QDBusConnection::sessionBus().interface())) { return; } bus->unregisterService(d->serviceName); } void KDBusService::Activate(const QVariantMap &platform_data) { Q_UNUSED(platform_data); #if HAVE_X11 if (QX11Info::isPlatformX11()) { QX11Info::setAppTime(QX11Info::getTimestamp()); } #endif // TODO QX11Info::setNextStartupId(platform_data.value("desktop-startup-id")) emit activateRequested(QStringList(), QString()); // TODO (via hook) KStartupInfo::appStarted(platform_data.value("desktop-startup-id")) // ^^ same discussion as below } void KDBusService::Open(const QStringList &uris, const QVariantMap &platform_data) { Q_UNUSED(platform_data); // TODO QX11Info::setNextStartupId(platform_data.value("desktop-startup-id")) emit openRequested(QUrl::fromStringList(uris)); // TODO (via hook) KStartupInfo::appStarted(platform_data.value("desktop-startup-id")) // ^^ not needed if the app actually opened a new window. // Solution 1: do it all the time anyway (needs API in QX11Info) // Solution 2: pass the id to the app and let it use KStartupInfo::appStarted if reusing a window } void KDBusService::ActivateAction(const QString &action_name, const QVariantList &maybeParameter, const QVariantMap &platform_data) { Q_UNUSED(platform_data); // This is a workaround for DBus not supporting null variants. const QVariant param = maybeParameter.count() == 1 ? maybeParameter.first() : QVariant(); emit activateActionRequested(action_name, param); // TODO (via hook) KStartupInfo::appStarted(platform_data.value("desktop-startup-id")) // if desktop-startup-id is set, the action is supposed to show a window (since it could be // called when the app is not running) } int KDBusService::CommandLine(const QStringList &arguments, const QString &workingDirectory, const QVariantMap &platform_data) { Q_UNUSED(platform_data); d->exitValue = 0; // The TODOs here only make sense if this method can be called from the GUI. // If it's for pure "usage in the terminal" then no startup notification got started. // But maybe one day the workspace wants to call this for the Exec key of a .desktop file? // TODO QX11Info::setNextStartupId(platform_data.value("desktop-startup-id")) emit activateRequested(arguments, workingDirectory); // TODO (via hook) KStartupInfo::appStarted(platform_data.value("desktop-startup-id")) return d->exitValue; } diff --git a/src/kdbusservice.h b/src/kdbusservice.h index 15172d8..1dcc29a 100644 --- a/src/kdbusservice.h +++ b/src/kdbusservice.h @@ -1,255 +1,262 @@ /* This file is part of libkdbusaddons Copyright (c) 2011 David Faure Copyright (c) 2011 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KDBUSSERVICE_H #define KDBUSSERVICE_H #include #include #include class KDBusServicePrivate; /** * KDBusService takes care of registering the current process with D-Bus. * * This registers the application at a predictable location on D-Bus, registers * the QCoreApplication (or subclass) object at /MainApplication, and * assists in implementing the application side of D-Bus activation from * the Desktop * Entry Specification. * * An application can either work in Multiple mode or Unique mode. * * In Multiple mode, the application can be launched many times. The service * name in the D-Bus registration will contain the PID to distinguish the * various instances; for example: org.kde.konqueror-12345. * * In Unique mode, only one instance of this application can ever run. * The first instance of the application registers with D-Bus without the PID, * and any attempt to run the application again will cause the * activateRequested() signal to be emitted in the already-running instance; the * duplicate instance will then quit. The exit value can be set by the already * running instance with setExitValue(), the default value is @c 0. * * Unique-mode applications should usually delay parsing command-line arguments * until after creating a KDBusService object; that way they know they are the * original instance of the application. * * Applications that set the D-Bus activation entries in their desktop files * should use Unique mode and connect to the signals emitted by this class. * Note that the D-Bus interface is exported for Multiple-mode applications as * well, so it also makes sense for such applications to connect to the signals * emitted by this class. * * @note In order to avoid a race, the application should export its objects to * D-Bus before allowing the event loop to run (for example, by calling * QCoreApplication::exec()). Otherwise, the application will appear on the bus * before its objects are accessible via D-Bus, which could be a problem for * other applications or scripts which start the application in order to talk * D-Bus to it immediately. * * Example usage: * * @code QApplication app(argc, argv); app.setApplicationName("kuiserver"); app.setOrganizationDomain("kde.org"); // Create your dbus objects here // ... KDBusService service(KDBusService::Unique); // If this point is reached, this is the only running instance // i.e. org.kde.kuiserver has been registered return app.exec(); * @endcode * * @since 5.0 */ class KDBUSADDONS_EXPORT KDBusService : public QObject { Q_OBJECT public: /** Options to control the behaviour of KDBusService */ enum StartupOption { /** Indicates that only one instance of this application should ever * exist. * * Cannot be combined with @c Multiple. */ Unique = 1, /** Indicates that multiple instances of the application may exist. * * Cannot be combined with @c Unique. This is the default. */ Multiple = 2, /** Indicates that the application should not exit if it failed to * register with D-Bus. * * If not set, KDBusService will quit the application if it failed to * register the service with D-Bus or a @c Unique instance can not be * activated. A @c Multiple instance will exit with error code @c 1. * The exit value of a @c Unique instance can be set from the running * instance with setExitValue(), the default value is @c 0. */ NoExitOnFailure = 4 }; Q_ENUM(StartupOption) Q_DECLARE_FLAGS(StartupOptions, StartupOption) Q_FLAG(StartupOptions) /** * Tries to register the current process to D-Bus at an address based on the * application name and organization domain. * * The DBus service name is the reversed organization domain, followed by * the application name. If @p options includes the @c Multiple flag, the * application PID will be appended. For example, * @code * app.setApplicationName("kuiserver"); * app.setOrganizationDomain("kde.org"); * @endcode * will make KDBusService register as @c org.kde.kuiserver in @c Unique * mode, and @c org.kde.kuiserver-1234 (if the process has PID @c 1234) in * @c Multiple mode. */ explicit KDBusService(StartupOptions options = Multiple, QObject *parent = nullptr); /** * Destroys this object (but does not unregister the application). * * Deleting this object before unregister() is called (either manually or * because QCoreApplication::aboutToQuit() was emitted) could confuse * clients, who will see the service on the bus but will be unable to use * the activation methods. */ ~KDBusService(); /** * Returns true if the D-Bus registration succeeded. * * Note that this is only useful when specifying the option NoExitOnFailure. * Otherwise, the simple fact that this process is still running indicates * that the registration succeeded. */ bool isRegistered() const; + /** + * Returns the name of the DBus service registered by this class. + * Mostly useful when using the option Multiple. + * @since 5.33 + */ + QString serviceName() const; + /** * Returns the error message from the D-Bus registration if it failed. * * Note that this is only useful when specifying the option NoExitOnFailure. * Otherwise the process has quit by the time you can get a chance to call this. */ QString errorMessage() const; /** * Sets the exit value to be used for a duplicate instance. * * If this is a @c Unique application, a slot connected to * activateRequested() can use this to specify a non-zero exit value for the * duplicate instance. This would typically be done if invalid command-line * arguments are passed. * * Note that this will only work if the signal-slot connection type is * Qt::DirectConnection. * * @param value The exit value for the duplicate instance. */ void setExitValue(int value); Q_SIGNALS: /** * Signals that the application is to be activated. * * If this is a @c Unique application, when KDBusService is constructed in * subsequent instances of the application (ie: when the executable is run * when an instance is already running), it will cause this signal to be * emitted in the already-running instance (with the arguments passed to the * duplicate instance), and the duplicate instance will then exit. * * If this application's desktop file indicates that it supports D-Bus * activation (DBusActivatable=true), a command launcher may also call the Activate() * D-Bus method to trigger this signal. In this case, @p args will be empty. * * In single-window applications, the connected signal should typically * raise the window. * * @param arguments The arguments the executable was called with. * See QCoreApplication::arguments(). * * A typical implementation of the slot would be * @code * commandLineParser->parse(arguments); // same QCommandLineParser instance as the one used in main() * handleCmdLine(workingDirectory); // shared method with main(), which uses commandLineParser to handle options and positional arguments * // and for GUI applications, also terminate startup notification and activate the mainwindow: * KStartupInfo::setNewStartupId(mainWindow, KStartupInfo::startupId()); * KWindowSystem::forceActiveWindow(mainWindow->winId()); * @endcode * * @see setExitValue() */ void activateRequested(const QStringList &arguments, const QString &workingDirectory); /** * Signals that one or more files should be opened in the application. * * @param uris The URLs of the files to open. */ void openRequested(const QList &uris); /** * Signals that an application action should be triggered. * * See the desktop entry specification for more information. */ void activateActionRequested(const QString &actionName, const QVariant ¶meter); public Q_SLOTS: /** * Unregister from D-Bus. * * This is called automatically when the application is about to quit, to * make sure it doesn't keep receiving calls to its D-Bus interface while it * is doing final cleanups. */ void unregister(); private: // fdo.Application spec void Activate(const QVariantMap &platform_data); void Open(const QStringList &uris, const QVariantMap &platform_data); void ActivateAction(const QString &action_name, const QVariantList &maybeParameter, const QVariantMap &platform_data); friend class KDBusServiceAdaptor; // org.kde.KDBusService int CommandLine(const QStringList &arguments, const QString &workingDirectory, const QVariantMap &platform_data); friend class KDBusServiceExtensionsAdaptor; private: KDBusServicePrivate *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KDBusService::StartupOptions) #endif /* KDBUSSERVICE_H */