Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,118 @@
add_subdirectory(tools/kquitapp)
add_library(KF6DBusAddons)
add_library(KF6::DBusAddons ALIAS KF6DBusAddons)
set_target_properties(KF6DBusAddons PROPERTIES
VERSION ${KDBUSADDONS_VERSION}
SOVERSION ${KDBUSADDONS_SOVERSION}
EXPORT_NAME DBusAddons
)
ecm_create_qm_loader(KF6DBusAddons kdbusaddons6_qt)
target_sources(KF6DBusAddons PRIVATE
kdbusservice.cpp
kdbusservice.h
kdedmodule.cpp
kdedmodule.h
kupdatelaunchenvironmentjob.cpp
kupdatelaunchenvironmentjob.h
)
ecm_qt_declare_logging_category(KF6DBusAddons
HEADER kdbusaddons_debug.h
IDENTIFIER KDBUSADDONS_LOG
CATEGORY_NAME kf.dbusaddons
OLD_CATEGORY_NAMES kf5.kdbusaddons
DESCRIPTION "KDBusAddons"
EXPORT KDBUSADDONS
)
set(libkdbusaddons_dbus_SRCS)
qt_add_dbus_interface(libkdbusaddons_dbus_SRCS org.freedesktop.Application.xml FreeDesktopApplpicationIface)
qt_add_dbus_interface(libkdbusaddons_dbus_SRCS org.kde.KDBusService.xml KDBusServiceIface)
qt_add_dbus_adaptor(libkdbusaddons_dbus_SRCS
org.freedesktop.Application.xml
kdbusservice.h
KDBusService
kdbusservice_adaptor
KDBusServiceAdaptor)
qt_add_dbus_adaptor(libkdbusaddons_dbus_SRCS
org.kde.KDBusService.xml
kdbusservice.h
KDBusService
kdbusserviceextensions_adaptor
KDBusServiceExtensionsAdaptor)
target_sources(KF6DBusAddons PRIVATE
${libkdbusaddons_dbus_SRCS}
)
ecm_generate_export_header(KF6DBusAddons
BASE_NAME KDBusAddons
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_link_libraries(KF6DBusAddons PUBLIC Qt6::DBus)
if(FALSE)
target_link_libraries(KF6DBusAddons PRIVATE Qt6::GuiPrivate) # qtx11extras_p.h
endif()
target_include_directories(KF6DBusAddons INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KDBusAddons>")
configure_file(config-kdbusaddons.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdbusaddons.h )
ecm_generate_headers(KDBusAddons_HEADERS
HEADER_NAMES
KDBusService
KDEDModule
KUpdateLaunchEnvironmentJob
REQUIRED_HEADERS KDBusAddons_HEADERS
)
install(TARGETS KF6DBusAddons EXPORT KF6DBusAddonsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${KDBusAddons_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/kdbusaddons_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KDBusAddons COMPONENT Devel
)
ecm_qt_install_logging_categories(
EXPORT KDBUSADDONS
FILE kdbusaddons.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
if(BUILD_QCH)
ecm_add_qch(
KF6DBusAddons_QCH
NAME KDBusAddons
BASE_NAME KF6DBusAddons
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KDBusAddons_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6DBus_QCH
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KDBUSADDONS_EXPORT
KDBUSADDONS_DEPRECATED
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Extract strings from all source files.
# EXTRACT_TR_STRINGS extracts strings with lupdate and convert them to .pot with
# lconvert.
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/kdbusaddons6_qt.pot
@@ -0,0 +1 @@
#cmakedefine01 HAVE_X11
@@ -0,0 +1,347 @@
/*
This file is part of libkdbusaddons
SPDX-FileCopyrightText: 2011 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2011 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kdbusservice.h"
#include <QCoreApplication>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusReply>
#include "FreeDesktopApplpicationIface.h"
#include "KDBusServiceIface.h"
#include "config-kdbusaddons.h"
#if 0
#include <private/qtx11extras_p.h>
#endif
#include "kdbusaddons_debug.h"
#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('.'), Qt::SkipEmptyParts);
QString reversedDomain;
if (parts.isEmpty()) {
reversedDomain = QStringLiteral("local.");
} else {
for (const QString &part : parts) {
reversedDomain.prepend(QLatin1Char('.'));
reversedDomain.prepend(part);
}
}
return reversedDomain + app->applicationName();
}
static void handlePlatformData(const QVariantMap &platformData)
{
#if 0
if (QX11Info::isPlatformX11()) {
QByteArray desktopStartupId = platformData.value(QStringLiteral("desktop-startup-id")).toByteArray();
if (!desktopStartupId.isEmpty()) {
QX11Info::setNextStartupId(desktopStartupId);
}
}
#endif
const auto xdgActivationToken = platformData.value(QLatin1String("activation-token")).toByteArray();
if (!xdgActivationToken.isEmpty()) {
qputenv("XDG_ACTIVATION_TOKEN", xdgActivationToken);
}
}
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(
"DBus session bus not found. To circumvent this problem try the following command (with bash):\n"
" export $(dbus-launch)");
} else {
generateServiceName();
}
}
void run()
{
if (bus) {
registerOnBus();
}
if (!d->registered && ((options & KDBusService::NoExitOnFailure) == 0)) {
qCCritical(KDBUSADDONS_LOG) << qPrintable(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"));
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) {
qCWarning(KDBUSADDONS_LOG) << "Failed to register /MainApplication on DBus";
return;
}
objectRegistered = bus.registerObject(objectPath, s, QDBusConnection::ExportAdaptors);
if (!objectRegistered) {
qCWarning(KDBUSADDONS_LOG) << "Failed to register" << objectPath << "on DBus";
return;
}
attemptRegistration();
}
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;
#if 0
if (QX11Info::isPlatformX11()) {
QString startupId = QString::fromUtf8(qgetenv("DESKTOP_STARTUP_ID"));
if (startupId.isEmpty()) {
startupId = QString::fromUtf8(QX11Info::nextStartupId());
}
if (!startupId.isEmpty()) {
platform_data.insert(QStringLiteral("desktop-startup-id"), startupId);
}
}
#endif
if (qEnvironmentVariableIsSet("XDG_ACTIVATION_TOKEN")) {
platform_data.insert(QStringLiteral("activation-token"), qgetenv("XDG_ACTIVATION_TOKEN"));
}
if (QCoreApplication::arguments().count() > 1) {
OrgKdeKDBusServiceInterface iface(d->serviceName, objectPath, QDBusConnection::sessionBus());
iface.setTimeout(5 * 60 * 1000); // Application can take time to answer
QDBusReply<int> 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<void> 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("Failed to register name '") + d->serviceName + QLatin1String("' with DBUS - does this process have permission to use the name, and do no other processes own it already?");
}
}
void waitForRegistration()
{
QTimer quitTimer;
// We have to wait for the other application to quit completely which could take a while
quitTimer.start(8000);
connect(&quitTimer, &QTimer::timeout, &registrationLoop, &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.get(), options);
registration.run();
}
KDBusService::~KDBusService() = default;
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)
{
d->handlePlatformData(platform_data);
Q_EMIT activateRequested(QStringList(QCoreApplication::arguments()[0]), QDir::currentPath());
qunsetenv("XDG_ACTIVATION_TOKEN");
}
void KDBusService::Open(const QStringList &uris, const QVariantMap &platform_data)
{
d->handlePlatformData(platform_data);
Q_EMIT openRequested(QUrl::fromStringList(uris));
qunsetenv("XDG_ACTIVATION_TOKEN");
}
void KDBusService::ActivateAction(const QString &action_name, const QVariantList &maybeParameter, const QVariantMap &platform_data)
{
d->handlePlatformData(platform_data);
// This is a workaround for D-Bus not supporting null variants.
const QVariant param = maybeParameter.count() == 1 ? maybeParameter.first() : QVariant();
Q_EMIT activateActionRequested(action_name, param);
qunsetenv("XDG_ACTIVATION_TOKEN");
}
int KDBusService::CommandLine(const QStringList &arguments, const QString &workingDirectory, const QVariantMap &platform_data)
{
d->exitValue = 0;
d->handlePlatformData(platform_data);
// 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?
Q_EMIT activateRequested(arguments, workingDirectory);
qunsetenv("XDG_ACTIVATION_TOKEN");
return d->exitValue;
}
#include "kdbusservice.moc"
#include "moc_kdbusservice.cpp"
@@ -0,0 +1,297 @@
/*
This file is part of libkdbusaddons
SPDX-FileCopyrightText: 2011 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2011 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KDBUSSERVICE_H
#define KDBUSSERVICE_H
#include <QObject>
#include <QUrl>
#include <memory>
#include <kdbusaddons_export.h>
class KDBusServicePrivate;
/**
* @class KDBusService kdbusservice.h <KDBusService>
*
* 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 <a
* href="http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html">Desktop
* Entry Specification</a>.
*
* 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: <tt>org.kde.konqueror-12345</tt>.
*
* 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 entry (DBusActivatable=true) in
* their desktop files will 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 D-Bus 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
* @see StartupOptions
*/
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,
/**
* Indicates that if there's already a unique service running, to be quit and replaced
* with our own.
*
* If exported, it will try first quitting the service calling @c org.qtproject.Qt.QCoreApplication.quit,
* which is exported by KDBusService by default.
*
* @since 5.65
*/
Replace = 8
};
Q_ENUM(StartupOption)
/**
* Stores a combination of #StartupOption values.
*/
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 D-Bus 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() could confuse
* clients, who will see the service on the bus but will be unable to use
* the activation methods.
*/
~KDBusService() override;
/**
* 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 D-Bus 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, starting with the executable file name.
* See QCoreApplication::arguments().
* This can also be empty.
*
* A typical implementation of the signal handler would be:
* @code
* if (!arguments.isEmpty()) {
* 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
* }
* @endcode
*
* For GUI applications, the handler also needs to deal with any platform-specific startup ids
* and make sure the mainwindow is shown as well as request its activation from the window manager.
* For X11, KDBusService makes the id for the Startup Notification protocol available
* from QX11Info::nextStartupId(), if there is one.
* For Wayland, KDBusService provides the token for the XDG Activation protocol in the
* "XDG_ACTIVATION_TOKEN" environment variable and unsets it again after the signal, if there is one.
* The util method @c KWindowSystem::updateStartupId(QWindow *window) (since KF 5.91) takes care of that.
* A typical implementation in the signal handler would be:
* @code
* mainWindow->show();
* KWindowSystem::updateStartupId(mainWindow->windowHandle());
* mainWindow->raise();
* KWindowSystem::activateWindow(mainWindow->windowHandle());
* @endcode
*
* If you're using the builtin handling of @c --help and @c --version in QCommandLineParser,
* you should call parser.process(arguments) before creating the KDBusService instance,
* since parse() doesn't handle those (and exiting the already-running instance would be wrong anyway).
*
* @see setExitValue()
*/
void activateRequested(const QStringList &arguments, const QString &workingDirectory);
/**
* Signals that one or more files should be opened in the application.
*
* This signal is emitted to request handling of the respective method of the D-Bus activation.
* For GUI applications, the signal handler also needs to deal with any platform-specific startup ids
* and make sure the mainwindow is shown as well as request its activation from the window manager.
* See documentation of activateRequested(const QStringList &arguments, const QString &)
* for details.
*
* @param uris The URLs of the files to open.
*/
void openRequested(const QList<QUrl> &uris);
/**
* Signals that an application action should be triggered.
*
* This signal is emitted to request handling of the respective method of the D-Bus activation.
* For GUI applications, the signal handler also needs to deal with any platform-specific startup ids
* and make sure the mainwindow is shown as well as request its activation from the window manager.
* See documentation of activateRequested(const QStringList &arguments, const QString &)
* for details.
*
* See the desktop entry specification for more information about action activation.
*/
void activateActionRequested(const QString &actionName, const QVariant &parameter);
public Q_SLOTS:
/**
* Manually unregister the given serviceName from D-Bus.
*/
void unregister();
private:
// fdo.Application spec
KDBUSADDONS_NO_EXPORT void Activate(const QVariantMap &platform_data);
KDBUSADDONS_NO_EXPORT void Open(const QStringList &uris, const QVariantMap &platform_data);
KDBUSADDONS_NO_EXPORT void ActivateAction(const QString &action_name, const QVariantList &maybeParameter, const QVariantMap &platform_data);
friend class KDBusServiceAdaptor;
// org.kde.KDBusService
KDBUSADDONS_NO_EXPORT int CommandLine(const QStringList &arguments, const QString &workingDirectory, const QVariantMap &platform_data);
friend class KDBusServiceExtensionsAdaptor;
private:
std::unique_ptr<KDBusServicePrivate> const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KDBusService::StartupOptions)
#endif /* KDBUSSERVICE_H */
@@ -0,0 +1,104 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kdedmodule.h"
#include "kdbusaddons_debug.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusObjectPath>
class KDEDModulePrivate
{
public:
QString moduleName;
};
KDEDModule::KDEDModule(QObject *parent)
: QObject(parent)
, d(new KDEDModulePrivate)
{
}
KDEDModule::~KDEDModule()
{
}
void KDEDModule::setModuleName(const QString &name)
{
d->moduleName = name;
QDBusObjectPath realPath(QLatin1String("/modules/") + d->moduleName);
if (realPath.path().isEmpty()) {
qCWarning(KDBUSADDONS_LOG) << "The kded module name" << name << "is invalid!";
return;
}
QDBusConnection::RegisterOptions regOptions;
if (this->metaObject()->indexOfClassInfo("D-Bus Interface") != -1) {
// 1. There are kded modules that don't have a D-Bus interface.
// 2. qt 4.4.3 crashes when trying to emit signals on class without
// Q_CLASSINFO("D-Bus Interface", "<your interface>") but
// ExportSignal set.
// We try to solve that for now with just registering Properties and
// Adaptors. But we should investigate where the sense is in registering
// the module at all. Just for autoload? Is there a better solution?
regOptions = QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors;
} else {
// Full functional module. Register everything.
regOptions = QDBusConnection::ExportScriptableSlots //
| QDBusConnection::ExportScriptableProperties //
| QDBusConnection::ExportAdaptors;
qCDebug(KDBUSADDONS_LOG) << "Registration of kded module" << d->moduleName << "without D-Bus interface.";
}
if (!QDBusConnection::sessionBus().registerObject(realPath.path(), this, regOptions)) {
// Happens for khotkeys but the module works. Need some time to investigate.
qCDebug(KDBUSADDONS_LOG) << "registerObject() returned false for" << d->moduleName;
} else {
// qCDebug(KDBUSADDONS_LOG) << "registerObject() successful for" << d->moduleName;
// Fix deadlock with Qt 5.6: this has to be delayed until the dbus thread is unlocked
auto registeredSignal = [this, realPath]() {
moduleRegistered(realPath);
};
QMetaObject::invokeMethod(this, registeredSignal, Qt::QueuedConnection);
}
}
QString KDEDModule::moduleName() const
{
return d->moduleName;
}
static const char s_modules_path[] = "/modules/";
QString KDEDModule::moduleForMessage(const QDBusMessage &message)
{
if (message.type() != QDBusMessage::MethodCallMessage) {
return QString();
}
QString obj = message.path();
if (!obj.startsWith(QLatin1String(s_modules_path))) {
return QString();
}
// Remove the <s_modules_path> part
obj = obj.mid(strlen(s_modules_path));
// Remove the part after the modules name
const int index = obj.indexOf(QLatin1Char('/'));
if (index != -1) {
obj = obj.left(index);
}
return obj;
}
#include "moc_kdedmodule.cpp"
@@ -0,0 +1,90 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __KDEDMODULE_H__
#define __KDEDMODULE_H__
#include <kdbusaddons_export.h>
#include <QObject>
#include <memory>
class KDEDModulePrivate;
class Kded;
class QDBusObjectPath;
class QDBusMessage;
/**
* \class KDEDModule kdedmodule.h <KDEDModule>
*
* The base class for KDED modules.
*
* KDED modules are constructed as shared libraries that are loaded on-demand
* into the kded daemon at runtime.
*
* See https://invent.kde.org/frameworks/kded/-/blob/master/docs/HOWTO
* for documentation about writing kded modules.
*
* @author Waldo Bastian <bastian@kde.org>
*/
class KDBUSADDONS_EXPORT KDEDModule : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KDEDModule")
friend class Kded;
public:
/**
* Constructor
*/
explicit KDEDModule(QObject *parent = nullptr);
~KDEDModule() override;
/**
* Sets the name of the module, and uses it to register the module to D-Bus.
*
* For modules loaded as plugins by a daemon, this is called automatically
* by the daemon after loading the module. Module authors should NOT call this.
*/
void setModuleName(const QString &name);
QString moduleName() const;
/**
* Returns the module being called by this D-Bus message.
* Useful for autoloading modules in kded and similar daemons.
* @since 5.7
*/
static QString moduleForMessage(const QDBusMessage &message);
Q_SIGNALS:
/**
* Emitted when a mainwindow registers itself.
*/
void windowRegistered(qlonglong windowId);
/**
* Emitted when a mainwindow unregisters itself.
*/
void windowUnregistered(qlonglong windowId);
/**
* Emitted after the module is registered successfully with D-Bus
*
* @since 4.2
*/
void moduleRegistered(const QDBusObjectPath &path);
private:
std::unique_ptr<KDEDModulePrivate> const d;
};
#endif
@@ -0,0 +1,158 @@
/*
SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kupdatelaunchenvironmentjob.h"
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusMetaType>
#include <QDBusPendingReply>
#include <QTimer>
#include "kdbusaddons_debug.h"
class KUpdateLaunchEnvironmentJobPrivate
{
public:
explicit KUpdateLaunchEnvironmentJobPrivate(KUpdateLaunchEnvironmentJob *q);
void monitorReply(const QDBusPendingReply<> &reply);
static bool isPosixName(const QString &name);
static bool isSystemdApprovedValue(const QString &value);
KUpdateLaunchEnvironmentJob *q;
QProcessEnvironment environment;
int pendingReplies = 0;
};
KUpdateLaunchEnvironmentJobPrivate::KUpdateLaunchEnvironmentJobPrivate(KUpdateLaunchEnvironmentJob *q)
: q(q)
{
}
void KUpdateLaunchEnvironmentJobPrivate::monitorReply(const QDBusPendingReply<> &reply)
{
++pendingReplies;
auto *watcher = new QDBusPendingCallWatcher(reply, q);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this](QDBusPendingCallWatcher *watcher) {
watcher->deleteLater();
--pendingReplies;
if (pendingReplies == 0) {
Q_EMIT q->finished();
q->deleteLater();
}
});
}
KUpdateLaunchEnvironmentJob::KUpdateLaunchEnvironmentJob(const QProcessEnvironment &environment)
: d(new KUpdateLaunchEnvironmentJobPrivate(this))
{
d->environment = environment;
QTimer::singleShot(0, this, &KUpdateLaunchEnvironmentJob::start);
}
KUpdateLaunchEnvironmentJob::~KUpdateLaunchEnvironmentJob() = default;
void KUpdateLaunchEnvironmentJob::start()
{
qDBusRegisterMetaType<QMap<QString, QString>>();
QMap<QString, QString> dbusActivationEnv;
QStringList systemdUpdates;
for (const auto &varName : d->environment.keys()) {
if (!KUpdateLaunchEnvironmentJobPrivate::isPosixName(varName)) {
qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as name contains unsupported characters";
continue;
}
const QString value = d->environment.value(varName);
// plasma-session
QDBusMessage plasmaSessionMsg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Startup"),
QStringLiteral("/Startup"),
QStringLiteral("org.kde.Startup"),
QStringLiteral("updateLaunchEnv"));
plasmaSessionMsg.setArguments({QVariant::fromValue(varName), QVariant::fromValue(value)});
auto plasmaSessionReply = QDBusConnection::sessionBus().asyncCall(plasmaSessionMsg);
d->monitorReply(plasmaSessionReply);
// DBus-activation environment
dbusActivationEnv.insert(varName, value);
// _user_ systemd env
// Systemd has stricter parsing of valid environment variables
// https://github.com/systemd/systemd/issues/16704
// validate here
if (!KUpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(value)) {
qCWarning(KDBUSADDONS_LOG) << "Skipping syncing of environment variable " << varName << "as value contains unsupported characters";
continue;
}
const QString updateString = varName + QStringLiteral("=") + value;
systemdUpdates.append(updateString);
}
// DBus-activation environment
QDBusMessage dbusActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
QStringLiteral("/org/freedesktop/DBus"),
QStringLiteral("org.freedesktop.DBus"),
QStringLiteral("UpdateActivationEnvironment"));
dbusActivationMsg.setArguments({QVariant::fromValue(dbusActivationEnv)});
auto dbusActivationReply = QDBusConnection::sessionBus().asyncCall(dbusActivationMsg);
d->monitorReply(dbusActivationReply);
// _user_ systemd env
QDBusMessage systemdActivationMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
QStringLiteral("/org/freedesktop/systemd1"),
QStringLiteral("org.freedesktop.systemd1.Manager"),
QStringLiteral("SetEnvironment"));
systemdActivationMsg.setArguments({systemdUpdates});
auto systemdActivationReply = QDBusConnection::sessionBus().asyncCall(systemdActivationMsg);
d->monitorReply(systemdActivationReply);
}
bool KUpdateLaunchEnvironmentJobPrivate::isPosixName(const QString &name)
{
// Posix says characters like % should be 'tolerated', but it gives issues in practice.
// https://bugzilla.redhat.com/show_bug.cgi?id=1754395
// https://bugzilla.redhat.com/show_bug.cgi?id=1879216
// Ensure systemd compat by only allowing alphanumerics and _ in names.
bool first = true;
for (const QChar c : name) {
if (first && !c.isLetter() && c != QLatin1Char('_')) {
return false;
} else if (first) {
first = false;
} else if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
return false;
}
}
return !first;
}
bool KUpdateLaunchEnvironmentJobPrivate::isSystemdApprovedValue(const QString &value)
{
// systemd code checks that a value contains no control characters except \n \t
// effectively copied from systemd's string_has_cc
for (const char &it : value.toLatin1()) {
if (it == QLatin1Char('\n') || it == QLatin1Char('\t')) {
continue;
}
if (it > 0 && it < ' ') {
return false;
}
if (it == 127) {
return false;
}
}
return true;
}
#include "moc_kupdatelaunchenvironmentjob.cpp"
@@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KUPDATELAUNCHENVIRONMENTJOB_H
#define KUPDATELAUNCHENVIRONMENTJOB_H
#include <kdbusaddons_export.h>
#include <QProcessEnvironment>
#include <memory>
class QString;
class KUpdateLaunchEnvironmentJobPrivate;
/**
* @class KUpdateLaunchEnvironmentJob updatelaunchenvironmentjob.h <KUpdateLaunchEnvironmentJob>
*
* Job for updating the launch environment.
*
* This job adds or updates an environment variable in process environment that will be used
* when a process is launched:
* This includes:
* - DBus activation
* - Systemd units
* - Plasma-session
*
* Environment variables are sanitized before uploading.
*
* This object deletes itself after completion, similar to KJobs
*
* Porting from KF5 to KF6:
*
* The class UpdateLaunchEnvironmentJob was renamed to KUpdateLaunchEnvironmentJob.
*
* @since 6.0
*/
class KDBUSADDONS_EXPORT KUpdateLaunchEnvironmentJob : public QObject
{
Q_OBJECT
public:
explicit KUpdateLaunchEnvironmentJob(const QProcessEnvironment &environment);
~KUpdateLaunchEnvironmentJob() override;
Q_SIGNALS:
void finished();
private:
KDBUSADDONS_NO_EXPORT void start();
private:
std::unique_ptr<KUpdateLaunchEnvironmentJobPrivate> const d;
};
#endif
@@ -0,0 +1,20 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.freedesktop.Application'>
<method name='Activate'>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
<arg type='a{sv}' name='platform-data' direction='in'/>
</method>
<method name='Open'>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type='as' name='uris' direction='in'/>
<arg type='a{sv}' name='platform-data' direction='in'/>
</method>
<method name='ActivateAction'>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
<arg type='s' name='action_name' direction='in'/>
<arg type='av' name='parameter' direction='in'/>
<arg type='a{sv}' name='platform-data' direction='in'/>
</method>
</interface>
</node>
@@ -0,0 +1,15 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.kde.KDBusService'>
<method name='CommandLine'>
<arg type='as' name='arguments' direction='in'/>
<arg type='s' name='working-dir' direction='in'/>
<arg type='a{sv}' name='platform-data' direction='in' />
<arg type='i' name='exit-status' direction='out'/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
</method>
<!--
<property name='Busy' type='b' access='read'/>
-->
</interface>
</node>
@@ -0,0 +1,4 @@
add_executable(kquitapp6 kquitapp.cpp)
ecm_mark_nongui_executable(kquitapp6)
target_link_libraries(kquitapp6 Qt6::DBus)
install(TARGETS kquitapp6 ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
@@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2006 Aaron Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDBusInterface>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
app.setApplicationName(QStringLiteral("kquitapp"));
app.setApplicationVersion(QStringLiteral("2.0"));
QCommandLineParser parser;
parser.setApplicationDescription(QCoreApplication::translate("main", "Quit a D-Bus enabled application easily"));
parser.addOption(QCommandLineOption(QStringLiteral("service"),
QCoreApplication::translate("main", "Full service name, overrides application name provided"),
QStringLiteral("service")));
parser.addOption(QCommandLineOption(QStringLiteral("path"),
QCoreApplication::translate("main", "Path in the D-Bus interface to use"),
QStringLiteral("path"),
QStringLiteral("/MainApplication")));
parser.addPositionalArgument(QStringLiteral("[application]"), QCoreApplication::translate("main", "The name of the application to quit"));
parser.addHelpOption();
parser.addVersionOption();
parser.process(app);
QString service;
if (parser.isSet(QStringLiteral("service"))) {
service = parser.value(QStringLiteral("service"));
} else if (!parser.positionalArguments().isEmpty()) {
service = QStringLiteral("org.kde.%1").arg(parser.positionalArguments().at(0));
} else {
parser.showHelp(1);
}
QString path(parser.value(QStringLiteral("path")));
QDBusInterface interface(service, path);
if (!interface.isValid()) {
qWarning() << QCoreApplication::translate("main", "Application %1 could not be found using service %2 and path %3.")
.arg(parser.positionalArguments().at(0), service, path);
return 1;
}
interface.call(QStringLiteral("quit"));
QDBusError error = interface.lastError();
if (error.type() != QDBusError::NoError) {
qWarning() << QCoreApplication::translate("main", "Quitting application %1 failed. Error reported was:\n\n %2 : %3")
.arg(parser.positionalArguments().join(QStringLiteral(" ")), error.name(), error.message());
return 1;
}
return 0;
}