Changeset View
Changeset View
Standalone View
Standalone View
libtaskmanager/waylandtasksmodel.cpp
Show All 16 Lines | |||||
17 | You should have received a copy of the GNU Lesser General Public | 17 | You should have received a copy of the GNU Lesser General Public | ||
18 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | 18 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | *********************************************************************/ | 19 | *********************************************************************/ | ||
20 | 20 | | |||
21 | #include "waylandtasksmodel.h" | 21 | #include "waylandtasksmodel.h" | ||
22 | #include "tasktools.h" | 22 | #include "tasktools.h" | ||
23 | 23 | | |||
24 | #include <KActivities/ResourceInstance> | 24 | #include <KActivities/ResourceInstance> | ||
25 | #include <KDirWatch> | ||||
25 | #include <KRun> | 26 | #include <KRun> | ||
26 | #include <KService> | 27 | #include <KService> | ||
28 | #include <KSharedConfig> | ||||
27 | #include <KWayland/Client/connection_thread.h> | 29 | #include <KWayland/Client/connection_thread.h> | ||
28 | #include <KWayland/Client/plasmawindowmanagement.h> | 30 | #include <KWayland/Client/plasmawindowmanagement.h> | ||
29 | #include <KWayland/Client/registry.h> | 31 | #include <KWayland/Client/registry.h> | ||
30 | #include <KWayland/Client/surface.h> | 32 | #include <KWayland/Client/surface.h> | ||
31 | #include <KWindowSystem> | 33 | #include <KWindowSystem> | ||
32 | 34 | | |||
33 | #include <QGuiApplication> | 35 | #include <QGuiApplication> | ||
34 | #include <QQuickItem> | 36 | #include <QQuickItem> | ||
35 | #include <QQuickWindow> | 37 | #include <QQuickWindow> | ||
36 | #include <QSet> | 38 | #include <QSet> | ||
37 | #include <QUrl> | 39 | #include <QUrl> | ||
38 | #include <QWindow> | 40 | #include <QWindow> | ||
39 | 41 | | |||
40 | namespace TaskManager | 42 | namespace TaskManager | ||
41 | { | 43 | { | ||
42 | 44 | | |||
43 | class WaylandTasksModel::Private | 45 | class WaylandTasksModel::Private | ||
44 | { | 46 | { | ||
45 | public: | 47 | public: | ||
46 | Private(WaylandTasksModel *q); | 48 | Private(WaylandTasksModel *q); | ||
47 | QList<KWayland::Client::PlasmaWindow*> windows; | 49 | QList<KWayland::Client::PlasmaWindow*> windows; | ||
48 | QHash<KWayland::Client::PlasmaWindow*, AppData> appDataCache; | 50 | QHash<KWayland::Client::PlasmaWindow*, AppData> appDataCache; | ||
49 | KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr; | 51 | KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr; | ||
52 | KSharedConfig::Ptr rulesConfig; | ||||
53 | KDirWatch *configWatcher = nullptr; | ||||
50 | 54 | | |||
55 | void init(); | ||||
51 | void initWayland(); | 56 | void initWayland(); | ||
52 | void addWindow(KWayland::Client::PlasmaWindow *window); | 57 | void addWindow(KWayland::Client::PlasmaWindow *window); | ||
53 | 58 | | |||
54 | AppData appData(KWayland::Client::PlasmaWindow *window); | 59 | AppData appData(KWayland::Client::PlasmaWindow *window); | ||
55 | 60 | | |||
61 | QIcon icon(KWayland::Client::PlasmaWindow *window); | ||||
62 | | ||||
56 | void dataChanged(KWayland::Client::PlasmaWindow *window, int role); | 63 | void dataChanged(KWayland::Client::PlasmaWindow *window, int role); | ||
57 | void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles); | 64 | void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles); | ||
58 | 65 | | |||
59 | private: | 66 | private: | ||
60 | WaylandTasksModel *q; | 67 | WaylandTasksModel *q; | ||
61 | }; | 68 | }; | ||
62 | 69 | | |||
63 | WaylandTasksModel::Private::Private(WaylandTasksModel *q) | 70 | WaylandTasksModel::Private::Private(WaylandTasksModel *q) | ||
64 | : q(q) | 71 | : q(q) | ||
65 | { | 72 | { | ||
66 | } | 73 | } | ||
67 | 74 | | |||
75 | void WaylandTasksModel::Private::init() | ||||
76 | { | ||||
77 | auto clearCacheAndRefresh = [this] { | ||||
78 | if (!windows.count()) { | ||||
79 | return; | ||||
80 | } | ||||
81 | | ||||
82 | appDataCache.clear(); | ||||
83 | | ||||
84 | // Emit changes of all roles satisfied from app data cache. | ||||
85 | q->dataChanged(q->index(0, 0), q->index(windows.count() - 1, 0), | ||||
86 | QVector<int>{Qt::DecorationRole, AbstractTasksModel::AppId, | ||||
87 | AbstractTasksModel::AppName, AbstractTasksModel::GenericName, | ||||
88 | AbstractTasksModel::LauncherUrl, | ||||
89 | AbstractTasksModel::LauncherUrlWithoutIcon}); | ||||
90 | }; | ||||
91 | | ||||
92 | rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); | ||||
93 | configWatcher = new KDirWatch(q); | ||||
94 | | ||||
95 | foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) { | ||||
96 | configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc")); | ||||
97 | } | ||||
98 | | ||||
99 | auto rulesConfigChange = [this, &clearCacheAndRefresh] { | ||||
100 | rulesConfig->reparseConfiguration(); | ||||
101 | clearCacheAndRefresh(); | ||||
102 | }; | ||||
103 | | ||||
104 | QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange); | ||||
105 | QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange); | ||||
106 | QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange); | ||||
107 | | ||||
108 | initWayland(); | ||||
109 | } | ||||
110 | | ||||
68 | void WaylandTasksModel::Private::initWayland() | 111 | void WaylandTasksModel::Private::initWayland() | ||
69 | { | 112 | { | ||
70 | if (!KWindowSystem::isPlatformWayland()) { | 113 | if (!KWindowSystem::isPlatformWayland()) { | ||
71 | return; | 114 | return; | ||
72 | } | 115 | } | ||
73 | 116 | | |||
74 | KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q); | 117 | KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q); | ||
75 | 118 | | |||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Line(s) | 169 | if (row != -1) { | |||
130 | q->endRemoveRows(); | 173 | q->endRemoveRows(); | ||
131 | } | 174 | } | ||
132 | }; | 175 | }; | ||
133 | 176 | | |||
134 | QObject::connect(window, &KWayland::Client::PlasmaWindow::unmapped, q, removeWindow); | 177 | QObject::connect(window, &KWayland::Client::PlasmaWindow::unmapped, q, removeWindow); | ||
135 | QObject::connect(window, &QObject::destroyed, q, removeWindow); | 178 | QObject::connect(window, &QObject::destroyed, q, removeWindow); | ||
136 | 179 | | |||
137 | QObject::connect(window, &KWayland::Client::PlasmaWindow::titleChanged, q, | 180 | QObject::connect(window, &KWayland::Client::PlasmaWindow::titleChanged, q, | ||
138 | [window, this] { dataChanged(window, Qt::DisplayRole); } | 181 | [window, this] { this->dataChanged(window, Qt::DisplayRole); } | ||
139 | ); | 182 | ); | ||
140 | 183 | | |||
141 | QObject::connect(window, &KWayland::Client::PlasmaWindow::iconChanged, q, | 184 | QObject::connect(window, &KWayland::Client::PlasmaWindow::iconChanged, q, | ||
142 | [window, this] { dataChanged(window, Qt::DecorationRole); } | 185 | [window, this] { | ||
186 | // The icon in the AppData struct might come from PlasmaWindow if it wasn't | ||||
187 | // filled in by windowUrlFromMetadata+appDataFromUrl. | ||||
188 | // TODO: Don't evict the cache unnecessarily if this isn't the case. As icons | ||||
189 | // are currently very static on Wayland, this eviction is unlikely to happen | ||||
190 | // frequently as of now. | ||||
191 | appDataCache.remove(window); | ||||
192 | | ||||
193 | this->dataChanged(window, Qt::DecorationRole); | ||||
194 | } | ||||
143 | ); | 195 | ); | ||
144 | 196 | | |||
145 | QObject::connect(window, &KWayland::Client::PlasmaWindow::appIdChanged, q, | 197 | QObject::connect(window, &KWayland::Client::PlasmaWindow::appIdChanged, q, | ||
146 | [window, this] { | 198 | [window, this] { | ||
199 | // The AppData struct in the cache is derived from this and needs | ||||
200 | // to be evicted in favor of a fresh struct based on the changed | ||||
201 | // window metadata. | ||||
147 | appDataCache.remove(window); | 202 | appDataCache.remove(window); | ||
148 | 203 | | |||
149 | dataChanged(window, QVector<int>{AppId, AppName, GenericName, | 204 | // Refresh roles satisfied from the app data cache. | ||
205 | this->dataChanged(window, QVector<int>{AppId, AppName, GenericName, | ||||
150 | LauncherUrl, LauncherUrlWithoutIcon}); | 206 | LauncherUrl, LauncherUrlWithoutIcon}); | ||
151 | } | 207 | } | ||
152 | ); | 208 | ); | ||
153 | 209 | | |||
154 | QObject::connect(window, &KWayland::Client::PlasmaWindow::activeChanged, q, | 210 | QObject::connect(window, &KWayland::Client::PlasmaWindow::activeChanged, q, | ||
155 | [window, this] { this->dataChanged(window, IsActive); } | 211 | [window, this] { this->dataChanged(window, IsActive); } | ||
156 | ); | 212 | ); | ||
157 | 213 | | |||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Line(s) | |||||
222 | QObject::connect(window, &KWayland::Client::PlasmaWindow::demandsAttentionChanged, q, | 278 | QObject::connect(window, &KWayland::Client::PlasmaWindow::demandsAttentionChanged, q, | ||
223 | [window, this] { this->dataChanged(window, IsDemandingAttention); } | 279 | [window, this] { this->dataChanged(window, IsDemandingAttention); } | ||
224 | ); | 280 | ); | ||
225 | 281 | | |||
226 | QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q, | 282 | QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q, | ||
227 | [window, this] { this->dataChanged(window, SkipTaskbar); } | 283 | [window, this] { this->dataChanged(window, SkipTaskbar); } | ||
228 | ); | 284 | ); | ||
229 | 285 | | |||
286 | // NOTE: The pid will never actually change on a real system. But if it ever did ... | ||||
230 | QObject::connect(window, &KWayland::Client::PlasmaWindow::pidChanged, q, | 287 | QObject::connect(window, &KWayland::Client::PlasmaWindow::pidChanged, q, | ||
231 | [window, this] { this->dataChanged(window, AppPid); } | 288 | [window, this] { | ||
289 | // The AppData struct in the cache is derived from this and needs | ||||
290 | // to be evicted in favor of a fresh struct based on the changed | ||||
291 | // window metadata. | ||||
292 | appDataCache.remove(window); | ||||
293 | | ||||
davidedmundson: this could also cause the icon to change, who emits a signal for that? | |||||
294 | // Refresh roles satisfied from the app data cache. | ||||
295 | this->dataChanged(window, QVector<int>{AppId, AppName, GenericName, | ||||
296 | LauncherUrl, LauncherUrlWithoutIcon}); | ||||
297 | } | ||||
232 | ); | 298 | ); | ||
233 | } | 299 | } | ||
234 | 300 | | |||
235 | AppData WaylandTasksModel::Private::appData(KWayland::Client::PlasmaWindow *window) | 301 | AppData WaylandTasksModel::Private::appData(KWayland::Client::PlasmaWindow *window) | ||
236 | { | 302 | { | ||
237 | const auto &it = appDataCache.constFind(window); | 303 | const auto &it = appDataCache.constFind(window); | ||
238 | 304 | | |||
239 | if (it != appDataCache.constEnd()) { | 305 | if (it != appDataCache.constEnd()) { | ||
240 | return *it; | 306 | return *it; | ||
241 | } | 307 | } | ||
242 | 308 | | |||
243 | const AppData &data = appDataFromAppId(window->appId()); | 309 | const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(), | ||
310 | window->pid(), rulesConfig)); | ||||
244 | 311 | | |||
245 | appDataCache.insert(window, data); | 312 | appDataCache.insert(window, data); | ||
246 | 313 | | |||
247 | return data; | 314 | return data; | ||
248 | } | 315 | } | ||
249 | 316 | | |||
317 | QIcon WaylandTasksModel::Private::icon(KWayland::Client::PlasmaWindow *window) | ||||
318 | { | ||||
319 | const AppData &app = appData(window); | ||||
320 | | ||||
321 | if (!app.icon.isNull()) { | ||||
322 | return app.icon; | ||||
323 | } | ||||
324 | | ||||
325 | appDataCache[window].icon = window->icon(); | ||||
326 | | ||||
327 | return window->icon(); | ||||
328 | } | ||||
329 | | ||||
250 | void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role) | 330 | void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role) | ||
251 | { | 331 | { | ||
252 | QModelIndex idx = q->index(windows.indexOf(window)); | 332 | QModelIndex idx = q->index(windows.indexOf(window)); | ||
253 | emit q->dataChanged(idx, idx, QVector<int>{role}); | 333 | emit q->dataChanged(idx, idx, QVector<int>{role}); | ||
254 | } | 334 | } | ||
255 | 335 | | |||
256 | void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles) | 336 | void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles) | ||
257 | { | 337 | { | ||
258 | QModelIndex idx = q->index(windows.indexOf(window)); | 338 | QModelIndex idx = q->index(windows.indexOf(window)); | ||
259 | emit q->dataChanged(idx, idx, roles); | 339 | emit q->dataChanged(idx, idx, roles); | ||
260 | } | 340 | } | ||
261 | 341 | | |||
262 | WaylandTasksModel::WaylandTasksModel(QObject *parent) | 342 | WaylandTasksModel::WaylandTasksModel(QObject *parent) | ||
263 | : AbstractWindowTasksModel(parent) | 343 | : AbstractWindowTasksModel(parent) | ||
264 | , d(new Private(this)) | 344 | , d(new Private(this)) | ||
265 | { | 345 | { | ||
266 | d->initWayland(); | 346 | d->init(); | ||
267 | } | 347 | } | ||
268 | 348 | | |||
269 | WaylandTasksModel::~WaylandTasksModel() = default; | 349 | WaylandTasksModel::~WaylandTasksModel() = default; | ||
270 | 350 | | |||
271 | QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const | 351 | QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const | ||
272 | { | 352 | { | ||
273 | if (!index.isValid() || index.row() >= d->windows.count()) { | 353 | if (!index.isValid() || index.row() >= d->windows.count()) { | ||
274 | return QVariant(); | 354 | return QVariant(); | ||
275 | } | 355 | } | ||
276 | 356 | | |||
277 | KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); | 357 | KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); | ||
278 | 358 | | |||
279 | if (role == Qt::DisplayRole) { | 359 | if (role == Qt::DisplayRole) { | ||
280 | return window->title(); | 360 | return window->title(); | ||
281 | } else if (role == Qt::DecorationRole) { | 361 | } else if (role == Qt::DecorationRole) { | ||
282 | return window->icon(); | 362 | return d->icon(window); | ||
283 | } else if (role == AppId) { | 363 | } else if (role == AppId) { | ||
364 | const QString &id = d->appData(window).id; | ||||
365 | | ||||
366 | if (id.isEmpty()) { | ||||
284 | return window->appId(); | 367 | return window->appId(); | ||
368 | } else { | ||||
369 | return id; | ||||
370 | } | ||||
285 | } else if (role == AppName) { | 371 | } else if (role == AppName) { | ||
286 | return d->appData(window).name; | 372 | return d->appData(window).name; | ||
287 | } else if (role == GenericName) { | 373 | } else if (role == GenericName) { | ||
288 | return d->appData(window).genericName; | 374 | return d->appData(window).genericName; | ||
289 | } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { | 375 | } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { | ||
290 | return d->appData(window).url; | 376 | return d->appData(window).url; | ||
291 | } else if (role == IsWindow) { | 377 | } else if (role == IsWindow) { | ||
292 | return true; | 378 | return true; | ||
▲ Show 20 Lines • Show All 258 Lines • Show Last 20 Lines |
this could also cause the icon to change, who emits a signal for that?