From 73c378832b040fe90ae42d4bc83fbedf6270c7ef Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 20 Aug 2021 12:39:51 +0300 Subject: [PATCH] [Task Manager] Add support for StartupTasksModel on Wayland BUG: 402903 --- libtaskmanager/CMakeLists.txt | 2 + libtaskmanager/startuptasksmodel.cpp | 5 + libtaskmanager/waylandstartuptasksmodel.cpp | 207 ++++++++++++++++++++ libtaskmanager/waylandstartuptasksmodel.h | 29 +++ shell/org.kde.plasmashell.desktop.cmake | 2 +- 5 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 libtaskmanager/waylandstartuptasksmodel.cpp create mode 100644 libtaskmanager/waylandstartuptasksmodel.h diff --git a/libtaskmanager/CMakeLists.txt b/libtaskmanager/CMakeLists.txt index 51f3c3fec..b556fa70f 100644 --- a/libtaskmanager/CMakeLists.txt +++ b/libtaskmanager/CMakeLists.txt @@ -17,6 +17,7 @@ set(taskmanager_LIB_SRCS tasksmodel.cpp tasktools.cpp virtualdesktopinfo.cpp + waylandstartuptasksmodel.cpp waylandtasksmodel.cpp windowtasksmodel.cpp ) @@ -84,6 +85,7 @@ install(FILES tasksmodel.h tasktools.h virtualdesktopinfo.h + waylandstartuptasksmodel.h waylandtasksmodel.h windowtasksmodel.h ${CMAKE_CURRENT_BINARY_DIR}/taskmanager_export.h diff --git a/libtaskmanager/startuptasksmodel.cpp b/libtaskmanager/startuptasksmodel.cpp index a07bdfb49..c7ad7456b 100644 --- a/libtaskmanager/startuptasksmodel.cpp +++ b/libtaskmanager/startuptasksmodel.cpp @@ -9,6 +9,7 @@ #include +#include "waylandstartuptasksmodel.h" #if HAVE_X11 #include "xstartuptasksmodel.h" #endif @@ -53,6 +54,10 @@ StartupTasksModel::Private::~Private() void StartupTasksModel::Private::initSourceTasksModel() { + if (!sourceTasksModel && KWindowSystem::isPlatformWayland()) { + sourceTasksModel = new WaylandStartupTasksModel(); + } + #if HAVE_X11 if (!sourceTasksModel && KWindowSystem::isPlatformX11()) { sourceTasksModel = new XStartupTasksModel(); diff --git a/libtaskmanager/waylandstartuptasksmodel.cpp b/libtaskmanager/waylandstartuptasksmodel.cpp new file mode 100644 index 000000000..cd15e5516 --- /dev/null +++ b/libtaskmanager/waylandstartuptasksmodel.cpp @@ -0,0 +1,207 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "waylandstartuptasksmodel.h" +#include "tasktools.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace TaskManager +{ +class Q_DECL_HIDDEN WaylandStartupTasksModel::Private +{ +public: + Private(WaylandStartupTasksModel *q); + + void addActivation(KWayland::Client::PlasmaActivation *activation); + void removeActivation(KWayland::Client::PlasmaActivation *activation); + + void init(); + void loadConfig(); + + struct Startup { + QString name; + QIcon icon; + QString applicationId; + QUrl launcherUrl; + KWayland::Client::PlasmaActivation *activation; + }; + + WaylandStartupTasksModel *q; + KConfigWatcher::Ptr configWatcher = nullptr; + KWayland::Client::PlasmaActivationFeedback *feedback = nullptr; + KWayland::Client::Registry *registry = nullptr; + QVector startups; + std::chrono::seconds startupTimeout = std::chrono::seconds::zero(); +}; + +WaylandStartupTasksModel::Private::Private(WaylandStartupTasksModel *q) + : q(q) +{ +} + +void WaylandStartupTasksModel::Private::init() +{ + configWatcher = KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("klaunchrc"))); + QObject::connect(configWatcher.data(), &KConfigWatcher::configChanged, q, [this]() { + loadConfig(); + }); + + loadConfig(); +} + +void WaylandStartupTasksModel::Private::loadConfig() +{ + KConfigGroup feedbackConfig(configWatcher->config(), "FeedbackStyle"); + + if (!feedbackConfig.readEntry("TaskbarButton", true)) { + delete feedback; + feedback = nullptr; + delete registry; + registry = nullptr; + + q->beginResetModel(); + startups.clear(); + q->endResetModel(); + return; + } + + const KConfigGroup taskbarButtonConfig(configWatcher->config(), "TaskbarButtonSettings"); + startupTimeout = std::chrono::seconds(taskbarButtonConfig.readEntry("Timeout", 5)); + + if (!registry) { + using namespace KWayland::Client; + + ConnectionThread *connection = ConnectionThread::fromApplication(q); + if (!connection) { + return; + } + + registry = new Registry(q); + registry->create(connection); + + QObject::connect(registry, &Registry::plasmaActivationFeedbackAnnounced, q, [this](quint32 name, quint32 version) { + feedback = registry->createPlasmaActivationFeedback(name, version, q); + + QObject::connect(feedback, &PlasmaActivationFeedback::interfaceAboutToBeReleased, q, [this] { + q->beginResetModel(); + startups.clear(); + q->endResetModel(); + }); + + QObject::connect(feedback, &PlasmaActivationFeedback::activation, q, [this](PlasmaActivation *activation) { + addActivation(activation); + }); + }); + + registry->setup(); + } +} + +void WaylandStartupTasksModel::Private::addActivation(KWayland::Client::PlasmaActivation *activation) +{ + QObject::connect(activation, &KWayland::Client::PlasmaActivation::applicationId, q, [this, activation](const QString &appId) { + // The application id is guaranteed to be the desktop filename without ".desktop" + const QString desktopFileName = appId + QLatin1String(".desktop"); + const QString desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktopFileName); + if (desktopFilePath.isEmpty()) { + qWarning() << "Got invalid activation app_id:" << appId; + return; + } + + const QUrl launcherUrl(QStringLiteral("applications:") + desktopFileName); + const AppData appData = appDataFromUrl(QUrl::fromLocalFile(desktopFilePath)); + + const int count = startups.count(); + q->beginInsertRows(QModelIndex(), count, count); + startups.append(Startup{ + .name = appData.name, + .icon = appData.icon, + .applicationId = appId, + .launcherUrl = launcherUrl, + .activation = activation, + }); + q->endInsertRows(); + + // Remove the activation if it doesn't finish within certain time interval. + QTimer *timeoutTimer = new QTimer(activation); + QObject::connect(timeoutTimer, &QTimer::timeout, q, [this, activation]() { + removeActivation(activation); + }); + timeoutTimer->setSingleShot(true); + timeoutTimer->start(startupTimeout); + }); + + QObject::connect(activation, &KWayland::Client::PlasmaActivation::finished, q, [this, activation]() { + removeActivation(activation); + }); +} + +void WaylandStartupTasksModel::Private::removeActivation(KWayland::Client::PlasmaActivation *activation) +{ + int position = -1; + for (int i = 0; i < startups.count(); ++i) { + if (startups[i].activation == activation) { + position = i; + break; + } + } + if (position != -1) { + q->beginRemoveRows(QModelIndex(), position, position); + startups.removeAt(position); + q->endRemoveRows(); + } +} + +WaylandStartupTasksModel::WaylandStartupTasksModel(QObject *parent) + : AbstractTasksModel(parent) + , d(new Private(this)) +{ + d->init(); +} + +WaylandStartupTasksModel::~WaylandStartupTasksModel() +{ +} + +QVariant WaylandStartupTasksModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= d->startups.count()) { + return QVariant(); + } + + const auto &data = d->startups[index.row()]; + if (role == Qt::DisplayRole) { + return data.name; + } else if (role == Qt::DecorationRole) { + return data.icon; + } else if (role == AppId) { + return data.applicationId; + } else if (role == AppName) { + return data.name; + } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { + return data.launcherUrl; + } else if (role == IsStartup) { + return true; + } + + return QVariant(); +} + +int WaylandStartupTasksModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : d->startups.count(); +} + +} // namespace TaskManager diff --git a/libtaskmanager/waylandstartuptasksmodel.h b/libtaskmanager/waylandstartuptasksmodel.h new file mode 100644 index 000000000..6415921b4 --- /dev/null +++ b/libtaskmanager/waylandstartuptasksmodel.h @@ -0,0 +1,29 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include "abstracttasksmodel.h" + +namespace TaskManager +{ +class TASKMANAGER_EXPORT WaylandStartupTasksModel : public AbstractTasksModel +{ + Q_OBJECT + +public: + explicit WaylandStartupTasksModel(QObject *parent = nullptr); + ~WaylandStartupTasksModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + +private: + class Private; + QScopedPointer d; +}; + +} // namespace TaskManager diff --git a/shell/org.kde.plasmashell.desktop.cmake b/shell/org.kde.plasmashell.desktop.cmake index f15daf361..2a3adfc6f 100644 --- a/shell/org.kde.plasmashell.desktop.cmake +++ b/shell/org.kde.plasmashell.desktop.cmake @@ -64,5 +64,5 @@ Icon=plasmashell NoDisplay=true X-systemd-skip=true -X-KDE-Wayland-Interfaces=org_kde_plasma_window_management,org_kde_kwin_keystate,zkde_screencast_unstable_v1 +X-KDE-Wayland-Interfaces=org_kde_plasma_window_management,org_kde_kwin_keystate,zkde_screencast_unstable_v1,org_kde_plasma_activation_feedback X-KDE-DBUS-Restricted-Interfaces=org.kde.kwin.Screenshot,org.kde.KWin.ScreenShot2