Plasma-workspace

xstartuptasksmodel.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Eike Hein <hein@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 "xstartuptasksmodel.h"
8
9#include <KApplicationTrader>
10#include <KConfig>
11#include <KConfigGroup>
12#include <KDirWatch>
13#include <KService>
14#include <KStartupInfo>
15
16#include <QIcon>
17#include <QTimer>
18#include <QUrl>
19
20using namespace Qt::StringLiterals;
21
22namespace TaskManager
23{
24class Q_DECL_HIDDEN XStartupTasksModel::Private
25{
26public:
27 Private(XStartupTasksModel *q);
28 KDirWatch *configWatcher = nullptr;
29 KStartupInfo *startupInfo = nullptr;
30 QList<KStartupInfoId> startups;
32 QHash<QByteArray, QUrl> launcherUrls;
33
34 void init();
35 void loadConfig();
36 QUrl launcherUrl(const KStartupInfoData &data);
37
38private:
39 XStartupTasksModel *q;
40};
41
42XStartupTasksModel::Private::Private(XStartupTasksModel *q)
43 : q(q)
44{
45}
46
47void XStartupTasksModel::Private::init()
48{
49 configWatcher = new KDirWatch(q);
51
52 QObject::connect(configWatcher, &KDirWatch::dirty, [this] {
53 loadConfig();
54 });
55 QObject::connect(configWatcher, &KDirWatch::created, [this] {
56 loadConfig();
57 });
58 QObject::connect(configWatcher, &KDirWatch::deleted, [this] {
59 loadConfig();
60 });
61
62 loadConfig();
63}
64
65void XStartupTasksModel::Private::loadConfig()
66{
67 const KConfig _c(u"klaunchrc"_s);
68 KConfigGroup c(&_c, u"FeedbackStyle"_s);
69
70 if (!c.readEntry("TaskbarButton", true)) {
71 delete startupInfo;
72 startupInfo = nullptr;
73
74 q->beginResetModel();
75 startups.clear();
76 startupData.clear();
77 q->endResetModel();
78
79 return;
80 }
81
82 if (!startupInfo) {
83 startupInfo = new KStartupInfo(KStartupInfo::CleanOnCantDetect, q);
84
85 QObject::connect(startupInfo, &KStartupInfo::gotNewStartup, q, [this](const KStartupInfoId &id, const KStartupInfoData &data) {
86 if (startups.contains(id)) {
87 return;
88 }
89
90 const QString appId = data.applicationId();
91 const QString bin = data.bin();
92
93 for (const KStartupInfoData &known : std::as_const(startupData)) {
94 // Reject if we already have a startup notification for this app.
95 if (known.applicationId() == appId && known.bin() == bin) {
96 return;
97 }
98 }
99
100 const int count = startups.count();
101 q->beginInsertRows(QModelIndex(), count, count);
102 startups.append(id);
103 startupData.insert(id.id(), data);
104 launcherUrls.insert(id.id(), launcherUrl(data));
105 q->endInsertRows();
106 });
107
108 QObject::connect(startupInfo, &KStartupInfo::gotRemoveStartup, q, [this](const KStartupInfoId &id) {
109 // The order in which startups are cancelled and corresponding
110 // windows appear is not reliable. Add some grace time to make
111 // an overlap more likely, giving a proxy some time to arbitrate
112 // between the two.
113 QTimer::singleShot(500, q, [this, id]() {
114 const int row = startups.indexOf(id);
115
116 if (row != -1) {
117 q->beginRemoveRows(QModelIndex(), row, row);
118 startups.removeAt(row);
119 startupData.remove(id.id());
120 launcherUrls.remove(id.id());
121 q->endRemoveRows();
122 }
123 });
124 });
125
126 QObject::connect(startupInfo, &KStartupInfo::gotStartupChange, q, [this](const KStartupInfoId &id, const KStartupInfoData &data) {
127 const int row = startups.indexOf(id);
128 if (row != -1) {
129 startupData.insert(id.id(), data);
130 launcherUrls.insert(id.id(), launcherUrl(data));
131 QModelIndex idx = q->index(row);
132 Q_EMIT q->dataChanged(idx, idx);
133 }
134 });
135 }
136
137 c = KConfigGroup(&_c, u"TaskbarButtonSettings"_s);
138 startupInfo->setTimeout(c.readEntry("Timeout", 5));
139}
140
141QUrl XStartupTasksModel::Private::launcherUrl(const KStartupInfoData &data)
142{
143 QUrl launcherUrl;
144 KService::List services;
145
146 QString appId = data.applicationId();
147
148 // Try to match via desktop filename ...
149 if (!appId.isEmpty() && appId.endsWith(QLatin1String(".desktop"))) {
150 if (appId.startsWith(QLatin1Char('/'))) {
151 // Even if we have an absolute path, try resolving to a service first (Bug 385594)
153 if (!service) { // No luck, just return it verbatim
154 launcherUrl = QUrl::fromLocalFile(appId);
155 return launcherUrl;
156 }
157
158 // Fall-through to menuId() handling below
159 services = {service};
160 } else {
161 // turn into KService desktop entry name
162 appId.chop(strlen(".desktop"));
163
164 services = KApplicationTrader::query([&appId](const KService::Ptr &service) {
165 return service->desktopEntryName().compare(appId, Qt::CaseInsensitive) == 0;
166 });
167 }
168 }
169
170 const QString wmClass = QString::fromLocal8Bit(data.WMClass());
171
172 // Try StartupWMClass.
173 if (services.empty() && !wmClass.isEmpty()) {
174 services = KApplicationTrader::query([&wmClass](const KService::Ptr &service) {
175 return service->property<QString>(QStringLiteral("StartupWMClass")).compare(wmClass, Qt::CaseInsensitive) == 0;
176 });
177 }
178
179 const QString name = data.findName();
180
181 // Try via name ...
182 if (services.empty() && !name.isEmpty()) {
183 services = KApplicationTrader::query([&name](const KService::Ptr &service) {
184 return service->name().compare(name, Qt::CaseInsensitive) == 0;
185 });
186 }
187
188 if (!services.empty()) {
189 const QString &menuId = services.at(0)->menuId();
190
191 // applications: URLs are used to refer to applications by their KService::menuId
192 // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
193 if (!menuId.isEmpty()) {
194 return QUrl(QString(u"applications:" + menuId));
195 }
196
197 QString path = services.at(0)->entryPath();
198
199 if (path.isEmpty()) {
200 path = services.at(0)->exec();
201 }
202
203 if (!path.isEmpty()) {
204 launcherUrl = QUrl::fromLocalFile(path);
205 }
206 }
207
208 return launcherUrl;
209}
210
211XStartupTasksModel::XStartupTasksModel(QObject *parent)
212 : AbstractTasksModel(parent)
213 , d(new Private(this))
214{
215 d->init();
216}
217
218XStartupTasksModel::~XStartupTasksModel()
219{
220}
221
222QVariant XStartupTasksModel::data(const QModelIndex &index, int role) const
223{
224 if (!index.isValid() || index.row() >= d->startups.count()) {
225 return QVariant();
226 }
227
228 const QByteArray &id = d->startups.at(index.row()).id();
229
230 if (!d->startupData.contains(id)) {
231 return QVariant();
232 }
233
234 const KStartupInfoData &data = d->startupData.value(id);
235
236 if (role == Qt::DisplayRole) {
237 return data.findName();
238 } else if (role == Qt::DecorationRole) {
239 return QIcon::fromTheme(data.findIcon(), QIcon::fromTheme(QLatin1String("unknown")));
240 } else if (role == AppId) {
241 QString idFromPath = QUrl::fromLocalFile(data.applicationId()).fileName();
242
243 if (idFromPath.endsWith(QLatin1String(".desktop"))) {
244 idFromPath = idFromPath.left(idFromPath.length() - 8);
245 }
246
247 return idFromPath;
248 } else if (role == AppName) {
249 return data.findName();
250 } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
251 return d->launcherUrls.value(id);
252 } else if (role == IsStartup) {
253 return true;
254 } else if (role == IsVirtualDesktopsChangeable) {
255 return false;
256 } else if (role == VirtualDesktops) {
257 return QVariantList() << QVariant(data.desktop());
258 } else if (role == IsOnAllVirtualDesktops) {
259 return (data.desktop() == 0);
260 } else if (role == CanLaunchNewInstance) {
261 return false;
262 }
263
264 return AbstractTasksModel::data(index, role);
265}
266
267int XStartupTasksModel::rowCount(const QModelIndex &parent) const
268{
269 return parent.isValid() ? 0 : d->startups.count();
270}
271
272} // namespace TaskManager
void deleted(const QString &path)
void dirty(const QString &path)
void created(const QString &path)
static Ptr serviceByDesktopPath(const QString &_path)
QByteArray WMClass() const
QString applicationId() const
const QString & findIcon() const
const QString & bin() const
int desktop() const
const QString & findName() const
void gotRemoveStartup(const KStartupInfoId &id, const KStartupInfoData &data)
void gotStartupChange(const KStartupInfoId &id, const KStartupInfoData &data)
void gotNewStartup(const KStartupInfoId &id, const KStartupInfoData &data)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QString path(const QString &relativePath)
QString name(StandardAction id)
QCA_EXPORT void init()
char at(qsizetype i) const const
QIcon fromTheme(const QString &name)
const_reference at(qsizetype i) const const
bool empty() const const
bool isValid() const const
int row() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString writableLocation(StandardLocation type)
qsizetype count() const const
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
CaseInsensitive
DisplayRole
QTextStream & bin(QTextStream &stream)
QString fileName(ComponentFormattingOptions options) const const
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.