Plasma-workspace

waylandstartuptasksmodel.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "waylandstartuptasksmodel.h"
8#include "libtaskmanager_debug.h"
9#include "tasktools.h"
10
11#include <KConfigGroup>
12#include <KConfigWatcher>
13
14#include <qwayland-plasma-window-management.h>
15
16#include <QDebug>
17#include <QTimer>
18#include <QUrl>
19#include <QtWaylandClient/QWaylandClientExtensionTemplate>
20
21using namespace Qt::StringLiterals;
22
23namespace TaskManager
24{
25
26class PlasmaActivation : public QObject, public QtWayland::org_kde_plasma_activation
27{
29public:
30 PlasmaActivation(::org_kde_plasma_activation *object)
31 : QtWayland::org_kde_plasma_activation(object)
32 {
33 }
34 ~PlasmaActivation() override
35 {
36 destroy();
37 }
38 void org_kde_plasma_activation_app_id(const QString &app_id) override
39 {
40 Q_EMIT appId(app_id);
41 }
42 void org_kde_plasma_activation_finished() override
43 {
44 Q_EMIT finished();
45 }
47 void appId(const QString &appId);
48 void finished();
49};
50class PlasmaActivationFeedback : public QWaylandClientExtensionTemplate<PlasmaActivationFeedback>, public QtWayland::org_kde_plasma_activation_feedback
51{
52 Q_OBJECT
53public:
54 PlasmaActivationFeedback()
55 : QWaylandClientExtensionTemplate(1)
56 {
57 connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
58 if (!isActive()) {
59 destroy();
60 }
61 });
62 }
63 ~PlasmaActivationFeedback()
64 {
65 if (isActive()) {
66 destroy();
67 }
68 }
69Q_SIGNALS:
70 void newActivation(PlasmaActivation *activation);
71
72protected:
73 void org_kde_plasma_activation_feedback_activation(::org_kde_plasma_activation *id) override
74 {
75 Q_EMIT newActivation(new PlasmaActivation(id));
76 }
77};
78
79class Q_DECL_HIDDEN WaylandStartupTasksModel::Private
80{
81public:
82 Private(WaylandStartupTasksModel *q);
83
84 void addActivation(PlasmaActivation *activation);
85 void removeActivation(PlasmaActivation *activation);
86
87 void init();
88 void loadConfig();
89
90 struct Startup {
92 QIcon icon;
93 QString applicationId;
94 QUrl launcherUrl;
95 std::unique_ptr<PlasmaActivation> activation;
96 };
97
98 WaylandStartupTasksModel *q;
99 KConfigWatcher::Ptr configWatcher = nullptr;
100 std::unique_ptr<PlasmaActivationFeedback> feedback = nullptr;
101 std::vector<Startup> startups;
102 std::chrono::seconds startupTimeout = std::chrono::seconds::zero();
103};
104
105WaylandStartupTasksModel::Private::Private(WaylandStartupTasksModel *q)
106 : q(q)
107{
108}
109
110void WaylandStartupTasksModel::Private::init()
111{
112 configWatcher = KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("klaunchrc"), KConfig::NoGlobals));
113 QObject::connect(configWatcher.data(), &KConfigWatcher::configChanged, q, [this] {
114 loadConfig();
115 });
116
117 loadConfig();
118}
119
120void WaylandStartupTasksModel::Private::loadConfig()
121{
122 KConfigGroup feedbackConfig(configWatcher->config(), u"FeedbackStyle"_s);
123
124 if (!feedbackConfig.readEntry("TaskbarButton", true)) {
125 q->beginResetModel();
126 startups.clear();
127 feedback.reset();
128 q->endResetModel();
129 return;
130 }
131
132 const KConfigGroup taskbarButtonConfig(configWatcher->config(), u"TaskbarButtonSettings"_s);
133 startupTimeout = std::chrono::seconds(taskbarButtonConfig.readEntry("Timeout", 5));
134
135 feedback = std::make_unique<PlasmaActivationFeedback>();
136
137 QObject::connect(feedback.get(), &PlasmaActivationFeedback::activeChanged, q, [this] {
138 if (!feedback->isActive()) {
139 q->beginResetModel();
140 startups.clear();
141 q->endResetModel();
142 }
143 });
144
145 QObject::connect(feedback.get(), &PlasmaActivationFeedback::newActivation, q, [this](PlasmaActivation *activation) {
146 addActivation(activation);
147 });
148}
149
150void WaylandStartupTasksModel::Private::addActivation(PlasmaActivation *activation)
151{
152 QObject::connect(activation, &PlasmaActivation::appId, q, [this, activation](const QString &appId) {
153 // The application id is guaranteed to be the desktop filename without ".desktop"
154 const QString desktopFileName = appId + QLatin1String(".desktop");
155 const QString desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktopFileName);
156 if (desktopFilePath.isEmpty()) {
157 qCWarning(TASKMANAGER_DEBUG) << "Got invalid activation app_id:" << appId;
158 return;
159 }
160
161 const QUrl launcherUrl(QString(u"applications:" + desktopFileName));
162 const AppData appData = appDataFromUrl(QUrl::fromLocalFile(desktopFilePath));
163
164 const int count = startups.size();
165 q->beginInsertRows(QModelIndex(), count, count);
166 startups.push_back(Startup{
167 .name = appData.name,
168 .icon = appData.icon,
169 .applicationId = appId,
170 .launcherUrl = launcherUrl,
171 .activation = std::unique_ptr<PlasmaActivation>(activation),
172 });
173 q->endInsertRows();
174
175 // Remove the activation if it doesn't finish within certain time interval.
176 QTimer *timeoutTimer = new QTimer(activation);
177 QObject::connect(timeoutTimer, &QTimer::timeout, q, [this, activation]() {
178 removeActivation(activation);
179 });
180 timeoutTimer->setSingleShot(true);
181 timeoutTimer->start(startupTimeout);
182 });
183
184 QObject::connect(activation, &PlasmaActivation::finished, q, [this, activation]() {
185 removeActivation(activation);
186 });
187}
188
189void WaylandStartupTasksModel::Private::removeActivation(PlasmaActivation *activation)
190{
191 auto it = std::find_if(startups.begin(), startups.end(), [activation](const Startup &startup) {
192 return startup.activation.get() == activation;
193 });
194 if (it == startups.end()) {
195 return;
196 }
197 const int position = std::distance(startups.begin(), it);
198 q->beginRemoveRows(QModelIndex(), position, position);
199 startups.erase(it);
200 q->endRemoveRows();
201}
202
203WaylandStartupTasksModel::WaylandStartupTasksModel(QObject *parent)
204 : AbstractTasksModel(parent)
205 , d(new Private(this))
206{
207 d->init();
208}
209
210WaylandStartupTasksModel::~WaylandStartupTasksModel()
211{
212}
213
214QVariant WaylandStartupTasksModel::data(const QModelIndex &index, int role) const
215{
216 // Note: when index is valid, its row >= 0, so casting to unsigned is safe
217 if (!index.isValid() || static_cast<size_t>(index.row()) >= d->startups.size()) {
218 return QVariant();
219 }
220
221 const auto &data = d->startups[index.row()];
222 if (role == Qt::DisplayRole) {
223 return data.name;
224 } else if (role == Qt::DecorationRole) {
225 return data.icon;
226 } else if (role == AppId) {
227 return data.applicationId;
228 } else if (role == AppName) {
229 return data.name;
230 } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
231 return data.launcherUrl;
232 } else if (role == IsStartup) {
233 return true;
234 } else if (role == CanLaunchNewInstance) {
235 return false;
236 } else if (role == IsOnAllVirtualDesktops) {
237 return true;
238 }
239
240 return AbstractTasksModel::data(index, role);
241}
242
243int WaylandStartupTasksModel::rowCount(const QModelIndex &parent) const
244{
245 return parent.isValid() ? 0 : d->startups.size();
246}
247
248} // namespace TaskManager
249
250#include "waylandstartuptasksmodel.moc"
static Ptr create(const KSharedConfig::Ptr &config)
void configChanged(const KConfigGroup &group, const QByteArrayList &names)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString name(StandardAction id)
QCA_EXPORT void init()
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
bool isEmpty() const const
DisplayRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setSingleShot(bool singleShot)
void start()
void timeout()
QUrl fromLocalFile(const QString &localFile)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:14:59 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.