diff --git a/src/kdbusservice.cpp b/src/kdbusservice.cpp index dfd98d0..b853523 100644 --- a/src/kdbusservice.cpp +++ b/src/kdbusservice.cpp @@ -1,341 +1,341 @@ /* This file is part of libkdbusaddons SPDX-FileCopyrightText: 2011 David Faure SPDX-FileCopyrightText: 2011 Kevin Ottens SPDX-FileCopyrightText: 2019 Harald Sitter SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #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(); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) const QStringList parts = domain.split(QLatin1Char('.'), QString::SkipEmptyParts); #else const QStringList parts = domain.split(QLatin1Char('.'), Qt::SkipEmptyParts); #endif QString reversedDomain; if (parts.isEmpty()) { reversedDomain = QStringLiteral("local."); } else { for (const QString &part : parts) { reversedDomain.prepend(QLatin1Char('.')); reversedDomain.prepend(part); } } return reversedDomain + app->applicationName(); } bool registered; QString serviceName; QString errorMessage; int exitValue; }; // Wraps a serviceName registration. class Registration : public QObject { Q_OBJECT public: enum class Register { RegisterWitoutQueue, RegisterWithQueue }; Registration(KDBusService *s_, KDBusServicePrivate *d_, KDBusService::StartupOptions options_) : s(s_) , d(d_) , options(options_) { 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)"); } else { generateServiceName(); } } void run() { if (bus) { registerOnBus(); } if (!d->registered && ((options & KDBusService::NoExitOnFailure) == 0)) { qCritical() << d->errorMessage; exit(1); } } private: void generateServiceName() { d->serviceName = d->generateServiceName(); 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 & KDBusService::Multiple) { - const bool inSandbox = QFileInfo::exists(QStringLiteral("/flatpak-info")); + const bool inSandbox = QFileInfo::exists(QStringLiteral("/.flatpak-info")); if (inSandbox) { d->serviceName += QStringLiteral(".kdbus-") + QDBusConnection::sessionBus().baseService().replace(QRegularExpression(QStringLiteral("[\\.:]")), QStringLiteral("_")); } else { d->serviceName += QLatin1Char('-') + QString::number(QCoreApplication::applicationPid()); } } } void registerOnBus() { auto bus = QDBusConnection::sessionBus(); bool objectRegistered = false; objectRegistered = bus.registerObject(QStringLiteral("/MainApplication"), QCoreApplication::instance(), QDBusConnection::ExportAllSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportAdaptors); if (!objectRegistered) { qWarning() << "Failed to register /MainApplication on DBus"; return; } objectRegistered = bus.registerObject(objectPath, s, QDBusConnection::ExportAdaptors); if (!objectRegistered) { qWarning() << "Failed to register" << objectPath << "on DBus"; return; } attemptRegistration(); if (d->registered) { if (QCoreApplication *app = QCoreApplication::instance()) { connect(app, &QCoreApplication::aboutToQuit, s, &KDBusService::unregister); } } } void attemptRegistration() { Q_ASSERT(!d->registered); auto queueOption = QDBusConnectionInterface::DontQueueService; if (options & KDBusService::Unique) { // When a process crashes and gets auto-restarted by KCrash we may // be in this code path "too early". There is a bit of a delay // between the restart and the previous process dropping off of the // bus and thus releasing its registered names. As a result there // is a good chance that if we wait a bit the name will shortly // become registered. queueOption = QDBusConnectionInterface::QueueService; connect(bus, &QDBusConnectionInterface::serviceRegistered, this, [this](const QString &service) { if (service != d->serviceName) { return; } d->registered = true; registrationLoop.quit(); }); } d->registered = (bus->registerService(d->serviceName, queueOption) == QDBusConnectionInterface::ServiceRegistered); if (d->registered) { return; } if (options & KDBusService::Replace) { auto message = QDBusMessage::createMethodCall(d->serviceName, QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"), QStringLiteral("quit")); QDBusConnection::sessionBus().asyncCall(message); waitForRegistration(); } else if (options & KDBusService::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(); } } // service did not respond in a valid way.... // let's wait to see if our queued registration finishes perhaps. waitForRegistration(); } if (!d->registered) { // either multi service or failed to reclaim name d->errorMessage = QLatin1String("Couldn't register name '") + d->serviceName + QLatin1String("' with DBUS - another process owns it already!"); } } void waitForRegistration() { QTimer quitTimer; // Wait a bit longer when we know this instance was restarted. There's // a very good chance we'll eventually get the name once the defunct // process closes its sockets. quitTimer.start(qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED") ? 8000 : 2000); connect(&quitTimer, &QTimer::timeout, ®istrationLoop, &QEventLoop::quit); registrationLoop.exec(); } QDBusConnectionInterface *bus = nullptr; KDBusService *s = nullptr; KDBusServicePrivate *d = nullptr; KDBusService::StartupOptions options; QEventLoop registrationLoop; QString objectPath; }; KDBusService::KDBusService(StartupOptions options, QObject *parent) : QObject(parent), d(new KDBusServicePrivate) { new KDBusServiceAdaptor(this); new KDBusServiceExtensionsAdaptor(this); Registration registration(this, d, options); registration.run(); } 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 D-Bus 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; } #include "kdbusservice.moc"