Changeset View
Changeset View
Standalone View
Standalone View
libtaskmanager/xwindowtasksmodel.cpp
- This file was added.
1 | /******************************************************************** | ||||
---|---|---|---|---|---|
2 | Copyright 2016 Eike Hein <hein@kde.org> | ||||
3 | | ||||
4 | This library is free software; you can redistribute it and/or | ||||
5 | modify it under the terms of the GNU Lesser General Public | ||||
6 | License as published by the Free Software Foundation; either | ||||
7 | version 2.1 of the License, or (at your option) version 3, or any | ||||
8 | later version accepted by the membership of KDE e.V. (or its | ||||
9 | successor approved by the membership of KDE e.V.), which shall | ||||
10 | act as a proxy defined in Section 6 of version 3 of the license. | ||||
11 | | ||||
12 | This library is distributed in the hope that it will be useful, | ||||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
15 | Lesser General Public License for more details. | ||||
16 | | ||||
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/>. | ||||
19 | *********************************************************************/ | ||||
20 | | ||||
21 | #include "xwindowtasksmodel.h" | ||||
22 | #include "tasktools.h" | ||||
23 | | ||||
24 | #include <KConfigGroup> | ||||
25 | #include <KDirWatch> | ||||
26 | #include <KIconLoader> | ||||
27 | #include <KRun> | ||||
28 | #include <KService> | ||||
29 | #include <KServiceTypeTrader> | ||||
30 | #include <KSharedConfig> | ||||
31 | #include <KStartupInfo> | ||||
32 | #include <KSycoca> | ||||
33 | #include <KWindowInfo> | ||||
34 | #include <KWindowSystem> | ||||
35 | #include <processcore/processes.h> | ||||
36 | #include <processcore/process.h> | ||||
37 | | ||||
38 | #include <QBuffer> | ||||
39 | #include <QDir> | ||||
40 | #include <QIcon> | ||||
41 | #include <QFile> | ||||
42 | #include <QGuiApplication> | ||||
43 | #include <QScreen> | ||||
44 | #include <QSet> | ||||
45 | #include <QStandardPaths> | ||||
46 | #include <QTimer> | ||||
47 | #include <QX11Info> | ||||
48 | | ||||
49 | #include <netwm.h> | ||||
50 | | ||||
51 | namespace TaskManager | ||||
52 | { | ||||
53 | | ||||
54 | static const NET::Properties windowInfoFlags = NET::WMState | NET::XAWMState | NET::WMDesktop | | ||||
55 | NET::WMVisibleName | NET::WMGeometry | | ||||
56 | NET::WMWindowType; | ||||
57 | static const NET::Properties2 windowInfoFlags2 = NET::WM2WindowClass | NET::WM2AllowedActions; | ||||
58 | | ||||
59 | class XWindowTasksModel::Private | ||||
60 | { | ||||
61 | public: | ||||
62 | Private(XWindowTasksModel *q); | ||||
63 | ~Private(); | ||||
64 | | ||||
65 | QVector<WId> windows; | ||||
66 | QSet<WId> transients; | ||||
67 | QHash<WId, WId> transientsDemandingAttention; | ||||
68 | QHash<WId, KWindowInfo*> windowInfoCache; | ||||
69 | QHash<WId, AppData> appDataCache; | ||||
70 | QHash<WId, QRect> delegateGeometries; | ||||
71 | WId activeWindow = -1; | ||||
72 | KSharedConfig::Ptr rulesConfig; | ||||
73 | KDirWatch *configWatcher = nullptr; | ||||
74 | QTimer sycocaChangeTimer; | ||||
75 | | ||||
76 | void init(); | ||||
77 | void addWindow(WId window); | ||||
78 | void removeWindow(WId window); | ||||
79 | void windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2); | ||||
80 | void transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2); | ||||
81 | void dataChanged(WId window, const QVector<int> &roles); | ||||
82 | | ||||
83 | KWindowInfo* windowInfo(WId window); | ||||
84 | AppData appData(WId window); | ||||
85 | | ||||
86 | QIcon icon(WId window); | ||||
87 | QString mimeType() const; | ||||
88 | QUrl windowUrl(WId window); | ||||
89 | QUrl launcherUrl(WId window); | ||||
90 | QUrl serviceUrl(int pid, const QString &type, const QStringList &cmdRemovals); | ||||
91 | KService::List servicesFromPid(int pid); | ||||
92 | int screen(WId window); | ||||
93 | QStringList activities(WId window); | ||||
94 | bool demandsAttention(WId window); | ||||
95 | | ||||
96 | private: | ||||
davidedmundson: windowInfoCache values leak in destructor | |||||
hein: Done. | |||||
97 | XWindowTasksModel *q; | ||||
98 | }; | ||||
99 | | ||||
100 | XWindowTasksModel::Private::Private(XWindowTasksModel *q) | ||||
101 | : q(q) | ||||
102 | { | ||||
103 | } | ||||
104 | | ||||
105 | XWindowTasksModel::Private::~Private() | ||||
106 | { | ||||
107 | qDeleteAll(windowInfoCache); | ||||
108 | windowInfoCache.clear(); | ||||
109 | } | ||||
110 | | ||||
111 | void XWindowTasksModel::Private::init() | ||||
112 | { | ||||
113 | rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); | ||||
114 | configWatcher = new KDirWatch(q); | ||||
115 | | ||||
116 | foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) { | ||||
graesslin: why not KSharedConfig::openConfig? | |||||
hein: Changed. | |||||
117 | configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc")); | ||||
118 | } | ||||
119 | | ||||
graesslin: coding style nitpick: missing space between foreach and ( | |||||
hein: Ok. | |||||
120 | QObject::connect(configWatcher, &KDirWatch::dirty, [this] { rulesConfig->reparseConfiguration(); }); | ||||
121 | QObject::connect(configWatcher, &KDirWatch::created, [this] { rulesConfig->reparseConfiguration(); }); | ||||
122 | QObject::connect(configWatcher, &KDirWatch::deleted, [this] { rulesConfig->reparseConfiguration(); }); | ||||
123 | | ||||
124 | sycocaChangeTimer.setSingleShot(true); | ||||
125 | sycocaChangeTimer.setInterval(100); | ||||
126 | | ||||
127 | QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, | ||||
128 | [this]() { | ||||
129 | if (!windows.count()) { | ||||
130 | return; | ||||
131 | } | ||||
132 | | ||||
133 | appDataCache.clear(); | ||||
134 | | ||||
135 | // Emit changes of all roles satisfied from app data cache. | ||||
136 | q->dataChanged(q->index(0, 0), q->index(windows.count() - 1, 0), | ||||
137 | QVector<int>{Qt::DecorationRole, AbstractTasksModel::AppId, | ||||
138 | AbstractTasksModel::AppName, AbstractTasksModel::GenericName, | ||||
139 | AbstractTasksModel::LauncherUrl}); | ||||
140 | } | ||||
141 | ); | ||||
142 | | ||||
143 | void (KSycoca::*myDatabaseChangeSignal)(const QStringList &) = &KSycoca::databaseChanged; | ||||
144 | QObject::connect(KSycoca::self(), myDatabaseChangeSignal, q, | ||||
145 | [this](const QStringList &changedResources) { | ||||
146 | if (changedResources.contains(QLatin1String("services")) | ||||
147 | || changedResources.contains(QLatin1String("apps")) | ||||
148 | || changedResources.contains(QLatin1String("xdgdata-apps"))) { | ||||
149 | sycocaChangeTimer.start(); | ||||
150 | } | ||||
151 | } | ||||
152 | ); | ||||
153 | | ||||
154 | QObject::connect(KWindowSystem::self(), &KWindowSystem::windowAdded, q, | ||||
155 | [this](WId window) { | ||||
156 | addWindow(window); | ||||
157 | } | ||||
158 | ); | ||||
159 | | ||||
160 | QObject::connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, q, | ||||
161 | [this](WId window) { | ||||
162 | removeWindow(window); | ||||
163 | } | ||||
164 | ); | ||||
165 | | ||||
166 | void (KWindowSystem::*myWindowChangeSignal)(WId window, | ||||
167 | NET::Properties properties, NET::Properties2 properties2) = &KWindowSystem::windowChanged; | ||||
168 | QObject::connect(KWindowSystem::self(), myWindowChangeSignal, q, | ||||
169 | [this](WId window, NET::Properties properties, NET::Properties2 properties2) { | ||||
you are connecting to a deprecated signal. see https://api.kde.org/frameworks/kwindowsystem/html/classKWindowSystem.html#ac8d368d83fa5e137f38e7c885d9a18ce graesslin: you are connecting to a deprecated signal. see https://api.kde. | |||||
hein: Changed. | |||||
170 | windowChanged(window, properties, properties2); | ||||
171 | } | ||||
172 | ); | ||||
173 | | ||||
174 | // Update IsActive for previously- and newly-active windows. | ||||
175 | QObject::connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, q, | ||||
176 | [this](WId window) { | ||||
177 | const WId oldActiveWindow = activeWindow; | ||||
178 | activeWindow = window; | ||||
179 | | ||||
180 | int row = windows.indexOf(oldActiveWindow); | ||||
181 | | ||||
182 | if (row != -1) { | ||||
183 | dataChanged(oldActiveWindow, QVector<int>{IsActive}); | ||||
184 | } | ||||
185 | | ||||
186 | row = windows.indexOf(window); | ||||
187 | | ||||
188 | if (row != -1) { | ||||
189 | dataChanged(window, QVector<int>{IsActive}); | ||||
190 | } | ||||
191 | } | ||||
192 | ); | ||||
193 | | ||||
194 | activeWindow = KWindowSystem::activeWindow(); | ||||
195 | | ||||
196 | // Add existing windows. | ||||
197 | foreach(const WId window, KWindowSystem::windows()) { | ||||
198 | addWindow(window); | ||||
199 | } | ||||
graesslin: coding style nitpick: missing whitespace | |||||
hein: Ok. | |||||
200 | } | ||||
201 | | ||||
202 | void XWindowTasksModel::Private::addWindow(WId window) | ||||
203 | { | ||||
204 | // Don't add window twice. | ||||
205 | if (windows.contains(window)) { | ||||
206 | return; | ||||
207 | } | ||||
208 | | ||||
209 | KWindowInfo info(window, | ||||
210 | NET::WMWindowType | NET::WMState | NET::WMName | NET::WMVisibleName, | ||||
211 | NET::WM2TransientFor); | ||||
212 | | ||||
213 | NET::WindowType wType = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | | ||||
214 | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | | ||||
215 | NET::OverrideMask | NET::TopMenuMask | | ||||
216 | NET::UtilityMask | NET::SplashMask); | ||||
217 | | ||||
218 | const WId leader = info.transientFor(); | ||||
219 | | ||||
220 | // Handle transient. | ||||
221 | if (leader > 0 && leader != QX11Info::appRootWindow() | ||||
222 | && !transients.contains(window) && windows.contains(leader)) { | ||||
223 | transients.insert(window); | ||||
224 | | ||||
225 | // Update demands attention state for leader. | ||||
226 | if (info.state() & NET::DemandsAttention && windows.contains(leader)) { | ||||
227 | transientsDemandingAttention.insertMulti(leader, window); | ||||
228 | dataChanged(leader, QVector<int>{IsDemandingAttention}); | ||||
229 | } | ||||
230 | | ||||
231 | return; | ||||
232 | } | ||||
233 | | ||||
234 | // Ignore NET::Tool and other special window types; they are not considered tasks. | ||||
235 | if (wType != NET::Normal && wType != NET::Override && wType != NET::Unknown && | ||||
236 | wType != NET::Dialog && wType != NET::Utility) { | ||||
237 | | ||||
238 | return; | ||||
239 | } | ||||
240 | | ||||
241 | const int count = windows.count(); | ||||
242 | q->beginInsertRows(QModelIndex(), count, count); | ||||
243 | windows.append(window); | ||||
244 | q->endInsertRows(); | ||||
245 | } | ||||
246 | | ||||
247 | void XWindowTasksModel::Private::removeWindow(WId window) | ||||
248 | { | ||||
249 | const int row = windows.indexOf(window); | ||||
250 | | ||||
251 | if (row != -1) { | ||||
252 | q->beginRemoveRows(QModelIndex(), row, row); | ||||
253 | windows.removeAt(row); | ||||
254 | transientsDemandingAttention.remove(window); | ||||
255 | windowInfoCache.remove(window); | ||||
256 | appDataCache.remove(window); | ||||
257 | delegateGeometries.remove(window); | ||||
258 | q->endRemoveRows(); | ||||
259 | } else { // Could be a transient. | ||||
260 | // Removing a transient might change the demands attention state of the leader. | ||||
261 | if (transients.remove(window)) { | ||||
262 | QMutableHashIterator<WId, WId> i(transientsDemandingAttention); | ||||
263 | WId leader = 0; | ||||
264 | | ||||
265 | while (i.hasNext()) { | ||||
266 | i.next(); | ||||
267 | | ||||
268 | if (i.value() == window) { | ||||
269 | if (leader == 0) { | ||||
270 | leader = i.key(); | ||||
271 | } | ||||
272 | | ||||
273 | i.remove(); | ||||
274 | } | ||||
275 | } | ||||
276 | | ||||
277 | if (leader != 0) { | ||||
278 | dataChanged(leader, QVector<int>{IsDemandingAttention}); | ||||
279 | } | ||||
280 | } | ||||
281 | } | ||||
282 | | ||||
283 | if (activeWindow == window) { | ||||
284 | activeWindow = -1; | ||||
285 | } | ||||
286 | } | ||||
287 | | ||||
288 | void XWindowTasksModel::Private::transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2) | ||||
289 | { | ||||
290 | // Changes to a transient's state might change demands attention state for leader. | ||||
291 | if (properties & (NET::WMState | NET::XAWMState)) { | ||||
292 | const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor); | ||||
293 | const WId leader = info.transientFor(); | ||||
294 | | ||||
295 | if (!windows.contains(leader)) { | ||||
296 | return; | ||||
297 | } | ||||
298 | | ||||
299 | if (info.state() & NET::DemandsAttention) { | ||||
300 | if (!transientsDemandingAttention.values(leader).contains(window)) { | ||||
301 | transientsDemandingAttention.insertMulti(leader, window); | ||||
302 | dataChanged(leader, QVector<int>{IsDemandingAttention}); | ||||
303 | } | ||||
304 | } else if (transientsDemandingAttention.remove(window)) { | ||||
305 | dataChanged(leader, QVector<int>{IsDemandingAttention}); | ||||
306 | } | ||||
307 | // Leader might have changed. | ||||
308 | } else if (properties2 & NET::WM2TransientFor) { | ||||
davidedmundson: We're missing some changed roles
IsKeepAbove
IsKeepBelow
SkipTaskbar | |||||
hein: Done. | |||||
309 | const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor); | ||||
310 | | ||||
311 | if (info.state() & NET::DemandsAttention) { | ||||
312 | WId oldLeader = info.transientFor(); | ||||
313 | QMutableHashIterator<WId, WId> i(transientsDemandingAttention); | ||||
314 | | ||||
davidedmundson: IsVirtualDesktopChangeable | |||||
hein: Done. | |||||
315 | while (i.hasNext()) { | ||||
316 | i.next(); | ||||
317 | | ||||
318 | if (i.value() == window) { | ||||
319 | i.remove(); | ||||
320 | oldLeader = i.key(); | ||||
321 | } | ||||
322 | } | ||||
323 | | ||||
324 | if (oldLeader != 0) { | ||||
325 | const WId leader = info.transientFor(); | ||||
326 | transientsDemandingAttention.insertMulti(leader, window); | ||||
327 | dataChanged(oldLeader, QVector<int>{IsDemandingAttention}); | ||||
328 | dataChanged(leader, QVector<int>{IsDemandingAttention}); | ||||
329 | } | ||||
330 | } | ||||
331 | } | ||||
332 | } | ||||
333 | | ||||
334 | void XWindowTasksModel::Private::windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2) | ||||
335 | { | ||||
336 | if (transients.contains(window)) { | ||||
337 | transientChanged(window, properties, properties2); | ||||
338 | | ||||
339 | return; | ||||
340 | } | ||||
341 | | ||||
342 | bool wipeInfoCache = false; | ||||
343 | bool wipeAppDataCache = false; | ||||
344 | QVector<int> changedRoles; | ||||
345 | | ||||
346 | if (properties & (NET::WMName | NET::WMVisibleName | NET::WM2WindowClass | NET::WMPid) | ||||
347 | || properties2 & NET::WM2WindowClass) { | ||||
348 | wipeInfoCache = true; | ||||
349 | wipeAppDataCache = true; | ||||
350 | changedRoles << Qt::DisplayRole << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl; | ||||
351 | } | ||||
352 | | ||||
353 | if ((properties & NET::WMIcon) && !changedRoles.contains(Qt::DecorationRole)) { | ||||
354 | changedRoles << Qt::DecorationRole; | ||||
355 | } | ||||
356 | | ||||
357 | // FIXME TODO: It might be worth keeping track of which windows were demanding | ||||
358 | // attention (or not) to avoid emitting this role on every state change, as | ||||
359 | // TaskGroupingProxyModel needs to check for group-ability when a change to it | ||||
360 | // is announced and the queried state is false. | ||||
361 | if (properties & (NET::WMState | NET::XAWMState)) { | ||||
362 | wipeInfoCache = true; | ||||
363 | changedRoles << IsFullScreen << IsMaximized << IsMinimized << IsKeepAbove << IsKeepBelow; | ||||
364 | changedRoles << IsShaded << IsDemandingAttention << SkipTaskbar; | ||||
365 | } | ||||
366 | | ||||
367 | if (properties2 & NET::WM2AllowedActions) { | ||||
368 | wipeInfoCache = true; | ||||
369 | changedRoles << IsClosable << IsMovable << IsResizable << IsMaximizable << IsMinimizable; | ||||
370 | changedRoles << IsFullScreenable << IsShadeable << IsVirtualDesktopChangeable; | ||||
371 | } | ||||
372 | | ||||
373 | if (properties & NET::WMDesktop) { | ||||
374 | wipeInfoCache = true; | ||||
375 | changedRoles << VirtualDesktop << IsOnAllVirtualDesktops; | ||||
376 | } | ||||
377 | | ||||
378 | if (properties & NET::WMGeometry) { | ||||
379 | wipeInfoCache = true; | ||||
380 | changedRoles << Screen; | ||||
381 | } | ||||
382 | | ||||
383 | if (properties2 & NET::WM2Activities) { | ||||
384 | changedRoles << Activities; | ||||
385 | } | ||||
386 | | ||||
387 | if (wipeInfoCache) { | ||||
388 | delete windowInfoCache.take(window); | ||||
389 | } | ||||
390 | | ||||
391 | if (wipeAppDataCache) { | ||||
392 | appDataCache.remove(window); | ||||
393 | } | ||||
394 | | ||||
395 | if (!changedRoles.isEmpty()) { | ||||
396 | dataChanged(window, changedRoles); | ||||
397 | } | ||||
398 | } | ||||
399 | | ||||
400 | void XWindowTasksModel::Private::dataChanged(WId window, const QVector<int> &roles) | ||||
401 | { | ||||
402 | const int i = windows.indexOf(window); | ||||
403 | | ||||
404 | if (i == -1) { | ||||
405 | return; | ||||
406 | } | ||||
407 | | ||||
408 | QModelIndex idx = q->index(i); | ||||
409 | emit q->dataChanged(idx, idx, roles); | ||||
410 | } | ||||
411 | | ||||
412 | KWindowInfo* XWindowTasksModel::Private::windowInfo(WId window) | ||||
413 | { | ||||
414 | if (!windowInfoCache.contains(window)) { | ||||
415 | KWindowInfo *info = new KWindowInfo(window, windowInfoFlags, windowInfoFlags2); | ||||
416 | windowInfoCache.insert(window, info); | ||||
417 | | ||||
418 | return info; | ||||
419 | } | ||||
420 | | ||||
I don't understand this valid check. Why are you inserting non valid KWindowInfo into the cache in the first place? graesslin: I don't understand this valid check. Why are you inserting non valid KWindowInfo into the cache… | |||||
Paranoia. I'm not inserting invalid ones into the cache, but I figured if there's a valid() it would be a good idea to call it before using it, so that block checked valid() and evicted if false. I've removed the block now. hein: Paranoia. I'm not inserting invalid ones into the cache, but I figured if there's a valid() it… | |||||
421 | return windowInfoCache.value(window); | ||||
422 | } | ||||
423 | | ||||
424 | AppData XWindowTasksModel::Private::appData(WId window) | ||||
425 | { | ||||
426 | if (!appDataCache.contains(window)) { | ||||
427 | const AppData &data = appDataFromUrl(windowUrl(window)); | ||||
428 | appDataCache.insert(window, data); | ||||
429 | | ||||
430 | return data; | ||||
431 | } | ||||
432 | | ||||
433 | return appDataCache.value(window); | ||||
434 | } | ||||
435 | | ||||
we don't want to open and parse a config file on every new window. davidedmundson: we don't want to open and parse a config file on every new window.
It'll be worth keeping the… | |||||
I've added a FIXME to optimize this at a later time. It's not worse than the old lib (it did this too). hein: I've added a FIXME to optimize this at a later time. It's not worse than the old lib (it did… | |||||
hein: Done. | |||||
436 | QIcon XWindowTasksModel::Private::icon(WId window) | ||||
437 | { | ||||
438 | const AppData &app = appData(window); | ||||
439 | | ||||
440 | if (!app.icon.isNull()) { | ||||
441 | return app.icon; | ||||
442 | } | ||||
443 | | ||||
444 | QIcon icon; | ||||
445 | | ||||
446 | icon.addPixmap(KWindowSystem::icon(window, KIconLoader::SizeSmall, KIconLoader::SizeSmall, false)); | ||||
447 | icon.addPixmap(KWindowSystem::icon(window, KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium, false)); | ||||
448 | icon.addPixmap(KWindowSystem::icon(window, KIconLoader::SizeMedium, KIconLoader::SizeMedium, false)); | ||||
449 | icon.addPixmap(KWindowSystem::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false)); | ||||
450 | | ||||
451 | return icon; | ||||
452 | } | ||||
453 | | ||||
454 | QString XWindowTasksModel::Private::mimeType() const | ||||
455 | { | ||||
456 | return QStringLiteral("windowsystem/winid"); | ||||
457 | } | ||||
458 | | ||||
459 | QUrl XWindowTasksModel::Private::windowUrl(WId window) | ||||
460 | { | ||||
461 | QUrl url; | ||||
462 | | ||||
463 | const KWindowInfo *info = windowInfo(window); | ||||
464 | const QString &classClass = info->windowClassClass(); | ||||
465 | const QString &className = info->windowClassName(); | ||||
466 | | ||||
467 | KService::List services; | ||||
468 | bool triedPid = false; | ||||
469 | | ||||
470 | if (!(classClass.isEmpty() && className.isEmpty())) { | ||||
471 | int pid = NETWinInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMPid, 0).pid(); | ||||
472 | | ||||
473 | // For KCModules, if we matched on window class, etc, we would end up matching to kcmshell4 - but we are more than likely | ||||
474 | // interested in the actual control module. Therefore we obtain this via the commandline. This commandline may contain | ||||
475 | // "kdeinit4:" or "[kdeinit]", so we remove these first. | ||||
476 | // FIXME This looks like ancient old crap we can do better now. | ||||
477 | if ("Kcmshell5" == classClass) { | ||||
478 | url = serviceUrl(pid, QStringLiteral("KCModule"), QStringList() << QStringLiteral("kdeinit5:") << QStringLiteral("[kdeinit]")); | ||||
479 | | ||||
480 | if (!url.isEmpty()) { | ||||
481 | return url; | ||||
482 | } | ||||
483 | } | ||||
484 | | ||||
485 | // Check to see if this wmClass matched a saved one ... | ||||
486 | KConfigGroup grp(rulesConfig, "Mapping"); | ||||
487 | KConfigGroup set(rulesConfig, "Settings"); | ||||
488 | | ||||
489 | // Some apps have different launchers depending upon command line ... | ||||
490 | QStringList matchCommandLineFirst = set.readEntry("MatchCommandLineFirst", QStringList()); | ||||
491 | | ||||
492 | if (!classClass.isEmpty() && matchCommandLineFirst.contains(classClass)) { | ||||
493 | triedPid = true; | ||||
494 | services = servicesFromPid(pid); | ||||
495 | } | ||||
496 | | ||||
497 | // Try to match using className also. | ||||
498 | if (!className.isEmpty() && matchCommandLineFirst.contains("::"+className)) { | ||||
499 | triedPid = true; | ||||
500 | services = servicesFromPid(pid); | ||||
501 | } | ||||
502 | | ||||
503 | // If the user has manualy set a mapping, respect this first... | ||||
504 | QString mapped(grp.readEntry(classClass + "::" + className, QString())); | ||||
505 | | ||||
506 | if (mapped.endsWith(QLatin1String(".desktop"))) { | ||||
507 | url = mapped; | ||||
508 | return url; | ||||
509 | } | ||||
510 | | ||||
511 | if (!classClass.isEmpty()) { | ||||
512 | if (mapped.isEmpty()) { | ||||
513 | mapped = grp.readEntry(classClass, QString()); | ||||
514 | | ||||
515 | if (mapped.endsWith(QLatin1String(".desktop"))) { | ||||
516 | url = mapped; | ||||
517 | return url; | ||||
518 | } | ||||
519 | } | ||||
520 | | ||||
521 | // Some apps, such as Wine, cannot use className to map to launcher name - as Wine itself is not a GUI app | ||||
522 | // So, Settings/ManualOnly lists window classes where the user will always have to manualy set the launcher ... | ||||
523 | QStringList manualOnly = set.readEntry("ManualOnly", QStringList()); | ||||
524 | | ||||
525 | if (!classClass.isEmpty() && manualOnly.contains(classClass)) { | ||||
526 | return url; | ||||
527 | } | ||||
528 | | ||||
529 | if (!mapped.isEmpty()) { | ||||
530 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(mapped)); | ||||
531 | } | ||||
532 | | ||||
533 | if (!mapped.isEmpty() && services.empty()) { | ||||
534 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name)").arg(mapped)); | ||||
535 | } | ||||
536 | | ||||
537 | // To match other docks (docky, unity, etc.) attempt to match on DesktopEntryName first ... | ||||
538 | if (services.empty()) { | ||||
539 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(classClass)); | ||||
540 | } | ||||
541 | | ||||
542 | // Try StartupWMClass. | ||||
543 | if (services.empty()) { | ||||
544 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(classClass)); | ||||
545 | } | ||||
546 | | ||||
547 | // Try 'Name' - unfortunately this can be translated, so has a good chance of failing! (As it does for KDE's own "System Settings" (even in English!!)) | ||||
548 | if (services.empty()) { | ||||
549 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name)").arg(classClass)); | ||||
550 | } | ||||
551 | } | ||||
552 | | ||||
553 | // Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ... | ||||
554 | if (services.empty() && !triedPid) { | ||||
555 | services = servicesFromPid(pid); | ||||
556 | } | ||||
557 | } | ||||
558 | | ||||
559 | // Try to improve on a possible from-binary fallback. | ||||
560 | // If no services were found or we got a fake-service back from getServicesViaPid() | ||||
561 | // we attempt to improve on this by adding a loosely matched reverse-domain-name | ||||
562 | // DesktopEntryName. Namely anything that is '*.classClass.desktop' would qualify here. | ||||
563 | // | ||||
564 | // Illustrative example of a case where the above heuristics would fail to produce | ||||
565 | // a reasonable result: | ||||
566 | // - org.kde.dragonplayer.desktop | ||||
567 | // - binary is 'dragon' | ||||
568 | // - qapp appname and thus classClass is 'dragonplayer' | ||||
569 | // - classClass cannot directly match the desktop file because of RDN | ||||
570 | // - classClass also cannot match the binary because of name mismatch | ||||
571 | // - in the following code *.classClass can match org.kde.dragonplayer though | ||||
572 | if (services.empty() || services.at(0)->desktopEntryName().isEmpty()) { | ||||
573 | auto matchingServices = KServiceTypeTrader::self()->query(QStringLiteral("Application"), | ||||
574 | QStringLiteral("exist Exec and ('%1' ~~ DesktopEntryName)").arg(classClass)); | ||||
575 | QMutableListIterator<KService::Ptr> it(matchingServices); | ||||
576 | while (it.hasNext()) { | ||||
577 | auto service = it.next(); | ||||
578 | if (!service->desktopEntryName().endsWith("." + classClass)) { | ||||
579 | it.remove(); | ||||
580 | } | ||||
581 | } | ||||
582 | // Exactly one match is expected, otherwise we discard the results as to reduce | ||||
583 | // the likelihood of false-positive mappings. Since we essentially eliminate the | ||||
584 | // uniqueness that RDN is meant to bring to the table we could potentially end | ||||
585 | // up with more than one match here. | ||||
586 | if (matchingServices.length() == 1) { | ||||
587 | services = matchingServices; | ||||
588 | } | ||||
589 | } | ||||
590 | | ||||
591 | if (!services.empty()) { | ||||
592 | QString path = services[0]->entryPath(); | ||||
593 | if (path.isEmpty()) { | ||||
594 | path = services[0]->exec(); | ||||
595 | } | ||||
596 | | ||||
597 | if (!path.isEmpty()) { | ||||
598 | url = QUrl::fromLocalFile(path); | ||||
599 | } | ||||
600 | } | ||||
601 | | ||||
602 | return url; | ||||
603 | } | ||||
604 | | ||||
605 | QUrl XWindowTasksModel::Private::launcherUrl(WId window) | ||||
606 | { | ||||
607 | const AppData &data = appData(window); | ||||
608 | | ||||
609 | if (!data.icon.name().isEmpty()) { | ||||
610 | return data.url; | ||||
611 | } | ||||
612 | | ||||
613 | const QIcon &i = icon(window); | ||||
614 | | ||||
615 | if (i.isNull()) { | ||||
616 | return data.url; | ||||
617 | } | ||||
618 | | ||||
619 | QUrl url = data.url; | ||||
620 | QUrlQuery uQuery(url); | ||||
621 | | ||||
622 | // FIXME Hard-coding 64px is not scaling-aware. | ||||
623 | const QPixmap pixmap = i.pixmap(QSize(64, 64)); | ||||
624 | QByteArray bytes; | ||||
625 | QBuffer buffer(&bytes); | ||||
626 | buffer.open(QIODevice::WriteOnly); | ||||
627 | pixmap.save(&buffer, "PNG"); | ||||
628 | uQuery.addQueryItem(QStringLiteral("iconData"), bytes.toBase64(QByteArray::Base64UrlEncoding)); | ||||
629 | | ||||
630 | url.setQuery(uQuery); | ||||
631 | | ||||
632 | return url; | ||||
633 | } | ||||
634 | | ||||
635 | QUrl XWindowTasksModel::Private::serviceUrl(int pid, const QString &type, const QStringList &cmdRemovals = QStringList()) | ||||
636 | { | ||||
637 | if (pid == 0) { | ||||
638 | return QUrl(); | ||||
639 | } | ||||
640 | | ||||
641 | KSysGuard::Processes procs; | ||||
642 | procs.updateOrAddProcess(pid); | ||||
643 | | ||||
644 | KSysGuard::Process *proc = procs.getProcess(pid); | ||||
645 | QString cmdline = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space??? | ||||
646 | | ||||
647 | if (cmdline.isEmpty()) { | ||||
648 | return QUrl(); | ||||
649 | } | ||||
650 | | ||||
651 | foreach (const QString & r, cmdRemovals) { | ||||
652 | cmdline.replace(r, QLatin1String("")); | ||||
653 | } | ||||
654 | | ||||
655 | KService::List services = KServiceTypeTrader::self()->query(type, QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline)); | ||||
656 | | ||||
657 | if (services.empty()) { | ||||
658 | // Could not find with complete command line, so strip out path part ... | ||||
659 | int slash = cmdline.lastIndexOf('/', cmdline.indexOf(' ')); | ||||
660 | if (slash > 0) { | ||||
661 | services = KServiceTypeTrader::self()->query(type, QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline.mid(slash + 1))); | ||||
662 | } | ||||
663 | | ||||
664 | if (services.empty()) { | ||||
665 | return QUrl(); | ||||
666 | } | ||||
667 | } | ||||
668 | | ||||
669 | if (!services.isEmpty()) { | ||||
670 | QString path = services[0]->entryPath(); | ||||
671 | | ||||
672 | if (!QDir::isAbsolutePath(path)) { | ||||
673 | QString absolutePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/"+path); | ||||
674 | if (!absolutePath.isEmpty()) | ||||
675 | path = absolutePath; | ||||
676 | } | ||||
677 | | ||||
678 | if (QFile::exists(path)) { | ||||
679 | return QUrl::fromLocalFile(path); | ||||
680 | } | ||||
681 | } | ||||
682 | | ||||
683 | return QUrl(); | ||||
684 | } | ||||
685 | | ||||
686 | KService::List XWindowTasksModel::Private::servicesFromPid(int pid) | ||||
687 | { | ||||
688 | // Attempt to find using commandline... | ||||
689 | KService::List services; | ||||
690 | | ||||
691 | if (pid == 0) { | ||||
692 | return services; | ||||
693 | } | ||||
694 | | ||||
695 | KSysGuard::Processes procs; | ||||
696 | procs.updateOrAddProcess(pid); | ||||
697 | | ||||
698 | KSysGuard::Process *proc = procs.getProcess(pid); | ||||
699 | QString cmdline = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space??? | ||||
700 | | ||||
701 | if (cmdline.isEmpty()) { | ||||
702 | return services; | ||||
703 | } | ||||
704 | | ||||
705 | const int firstSpace = cmdline.indexOf(' '); | ||||
706 | | ||||
707 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline)); | ||||
708 | if (services.empty()) { | ||||
709 | // Could not find with complete commandline, so strip out path part... | ||||
710 | int slash = cmdline.lastIndexOf('/', firstSpace); | ||||
711 | if (slash > 0) { | ||||
712 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline.mid(slash + 1))); | ||||
713 | } | ||||
714 | } | ||||
715 | | ||||
716 | if (services.empty() && firstSpace > 0) { | ||||
717 | // Could not find with arguments, so try without... | ||||
718 | cmdline = cmdline.left(firstSpace); | ||||
719 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline)); | ||||
720 | | ||||
721 | int slash = cmdline.lastIndexOf('/'); | ||||
722 | if (slash > 0) { | ||||
723 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline.mid(slash + 1))); | ||||
724 | } | ||||
725 | } | ||||
726 | | ||||
727 | if (services.empty() && proc && !QStandardPaths::findExecutable(cmdline).isEmpty()) { | ||||
728 | // cmdline now exists without arguments if there were any | ||||
729 | services << QExplicitlySharedDataPointer<KService>(new KService(proc->name(), cmdline, QString())); | ||||
730 | } | ||||
731 | return services; | ||||
732 | } | ||||
733 | | ||||
734 | int XWindowTasksModel::Private::screen(WId window) | ||||
735 | { | ||||
736 | const KWindowInfo *info = windowInfo(window); | ||||
737 | const QList<QScreen *> &screens = QGuiApplication::screens(); | ||||
738 | | ||||
739 | for (int i = 0; i < screens.count(); ++i) { | ||||
740 | if (screens.at(i)->geometry().intersects(info->geometry())) { | ||||
741 | return i; | ||||
742 | } | ||||
743 | } | ||||
744 | | ||||
745 | return -1; | ||||
746 | } | ||||
747 | | ||||
748 | QStringList XWindowTasksModel::Private::activities(WId window) | ||||
749 | { | ||||
750 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), 0, NET::WM2Activities); | ||||
751 | | ||||
752 | const QString result(ni.activities()); | ||||
753 | | ||||
754 | if (!result.isEmpty() && result != QLatin1String("00000000-0000-0000-0000-000000000000")) { | ||||
755 | return result.split(','); | ||||
756 | } | ||||
757 | | ||||
davidedmundson: you can do all this in the QByteArray ctor | |||||
hein: Done. | |||||
hein: Actually not done -- I'll ask you later about this. | |||||
hein: Done now. | |||||
758 | return QStringList(); | ||||
759 | } | ||||
760 | | ||||
761 | bool XWindowTasksModel::Private::demandsAttention(WId window) | ||||
762 | { | ||||
763 | if (windows.contains(window)) { | ||||
764 | return ((windowInfo(window)->state() & NET::DemandsAttention) | ||||
765 | || transientsDemandingAttention.contains(window)); | ||||
766 | } | ||||
767 | | ||||
768 | return false; | ||||
769 | } | ||||
770 | | ||||
771 | XWindowTasksModel::XWindowTasksModel(QObject *parent) | ||||
772 | : AbstractTasksModel(parent) | ||||
773 | , d(new Private(this)) | ||||
774 | { | ||||
775 | d->init(); | ||||
776 | } | ||||
777 | | ||||
778 | XWindowTasksModel::~XWindowTasksModel() | ||||
779 | { | ||||
780 | } | ||||
781 | | ||||
782 | QVariant XWindowTasksModel::data(const QModelIndex &index, int role) const | ||||
783 | { | ||||
784 | if (!index.isValid() || index.row() >= d->windows.count()) { | ||||
785 | return QVariant(); | ||||
786 | } | ||||
787 | | ||||
788 | const WId window = d->windows.at(index.row()); | ||||
789 | | ||||
790 | if (role == Qt::DisplayRole) { | ||||
791 | return d->windowInfo(window)->visibleName(); | ||||
792 | } else if (role == Qt::DecorationRole) { | ||||
793 | return d->icon(window); | ||||
794 | } else if (role == AppId) { | ||||
795 | return d->appData(window).id; | ||||
796 | } else if (role == AppName) { | ||||
797 | return d->appData(window).name; | ||||
798 | } else if (role == GenericName) { | ||||
799 | return d->appData(window).genericName; | ||||
800 | } else if (role == LauncherUrl) { | ||||
801 | return d->launcherUrl(window); | ||||
802 | } else if (role == LegacyWinIdList) { | ||||
803 | return QVariantList() << window; | ||||
804 | } else if (role == MimeType) { | ||||
805 | return d->mimeType(); | ||||
806 | } else if (role == MimeData) { | ||||
807 | return QByteArray((char*)&window, sizeof(window));; | ||||
808 | } else if (role == IsWindow) { | ||||
809 | return true; | ||||
810 | } else if (role == IsActive) { | ||||
811 | return (window == d->activeWindow); | ||||
812 | } else if (role == IsClosable) { | ||||
813 | return d->windowInfo(window)->actionSupported(NET::ActionClose); | ||||
814 | } else if (role == IsMovable) { | ||||
815 | return d->windowInfo(window)->actionSupported(NET::ActionMove); | ||||
816 | } else if (role == IsResizable) { | ||||
817 | return d->windowInfo(window)->actionSupported(NET::ActionResize); | ||||
818 | } else if (role == IsMaximizable) { | ||||
819 | return d->windowInfo(window)->actionSupported(NET::ActionMax); | ||||
820 | } else if (role == IsMaximized) { | ||||
821 | const KWindowInfo *info = d->windowInfo(window); | ||||
822 | return (bool)(info->state() & NET::MaxHoriz) && (bool)(info->state() & NET::MaxVert); | ||||
823 | } else if (role == IsMinimizable) { | ||||
824 | return d->windowInfo(window)->actionSupported(NET::ActionMinimize); | ||||
825 | } else if (role == IsMinimized) { | ||||
826 | return d->windowInfo(window)->isMinimized(); | ||||
827 | } else if (role == IsKeepAbove) { | ||||
828 | return (bool)(d->windowInfo(window)->state() & NET::StaysOnTop); | ||||
829 | } else if (role == IsKeepBelow) { | ||||
830 | return (bool)(d->windowInfo(window)->state() & NET::KeepBelow); | ||||
831 | } else if (role == IsFullScreenable) { | ||||
832 | return d->windowInfo(window)->actionSupported(NET::ActionFullScreen); | ||||
833 | } else if (role == IsFullScreen) { | ||||
834 | return (bool)(d->windowInfo(window)->state() & NET::FullScreen); | ||||
835 | } else if (role == IsShadeable) { | ||||
836 | return d->windowInfo(window)->actionSupported(NET::ActionShade); | ||||
837 | } else if (role == IsShaded) { | ||||
838 | return (bool)(d->windowInfo(window)->state() & NET::Shaded); | ||||
839 | } else if (role == IsVirtualDesktopChangeable) { | ||||
840 | return d->windowInfo(window)->actionSupported(NET::ActionChangeDesktop); | ||||
841 | } else if (role == VirtualDesktop) { | ||||
842 | return d->windowInfo(window)->desktop(); | ||||
843 | } else if (role == IsOnAllVirtualDesktops) { | ||||
844 | return d->windowInfo(window)->onAllDesktops(); | ||||
845 | } else if (role == Screen) { | ||||
846 | return d->screen(window); | ||||
847 | } else if (role == Activities) { | ||||
848 | return d->activities(window); | ||||
849 | } else if (role == IsDemandingAttention) { | ||||
850 | return d->demandsAttention(window); | ||||
851 | } else if (role == SkipTaskbar) { | ||||
852 | return (bool)(d->windowInfo(window)->state() & NET::SkipTaskbar); | ||||
853 | } | ||||
854 | | ||||
855 | return QVariant(); | ||||
856 | } | ||||
857 | | ||||
858 | int XWindowTasksModel::rowCount(const QModelIndex &parent) const | ||||
859 | { | ||||
860 | return parent.isValid() ? 0 : d->windows.count(); | ||||
861 | } | ||||
862 | | ||||
davidedmundson: what's with the Q_INVOKABLE? | |||||
hein: Copy and paste mistake probably, fixed. | |||||
863 | void XWindowTasksModel::requestActivate(const QModelIndex &index) | ||||
864 | { | ||||
865 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
866 | return; | ||||
867 | } | ||||
868 | | ||||
869 | if (index.row() >= 0 && index.row() < d->windows.count()) { | ||||
870 | WId window = d->windows.at(index.row()); | ||||
871 | | ||||
872 | // Pull forward any transient demanding attention. | ||||
873 | if (d->transientsDemandingAttention.contains(window)) { | ||||
874 | window = d->transientsDemandingAttention.value(window); | ||||
875 | // Quote from legacy libtaskmanager: | ||||
876 | // "this is a work around for (at least?) kwin where a shaded transient will prevent the main | ||||
877 | // window from being brought forward unless the transient is actually pulled forward, most | ||||
878 | // easily reproduced by opening a modal file open/save dialog on an app then shading the file | ||||
879 | // dialog and trying to bring the window forward by clicking on it in a tasks widget | ||||
880 | // TODO: do we need to check all the transients for shaded?" | ||||
881 | } else if (!d->transients.isEmpty()) { | ||||
882 | foreach (const WId transient, d->transients) { | ||||
883 | KWindowInfo info(transient, NET::WMState, NET::WM2TransientFor); | ||||
884 | | ||||
885 | if (info.valid(true) && (info.state() & NET::Shaded) && info.transientFor() == window) { | ||||
886 | window = transient; | ||||
887 | break; | ||||
888 | } | ||||
889 | } | ||||
890 | } | ||||
891 | | ||||
892 | KWindowSystem::forceActiveWindow(window); | ||||
893 | } | ||||
894 | } | ||||
895 | | ||||
896 | void XWindowTasksModel::requestNewInstance(const QModelIndex &index) | ||||
897 | { | ||||
898 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
899 | return; | ||||
900 | } | ||||
901 | | ||||
902 | const QUrl &url = d->appData(d->windows.at(index.row())).url; | ||||
903 | | ||||
904 | if (url.isValid()) { | ||||
905 | new KRun(url, 0, false, KStartupInfo::createNewStartupIdForTimestamp(QX11Info::appUserTime())); | ||||
906 | } | ||||
907 | } | ||||
908 | | ||||
909 | void XWindowTasksModel::requestClose(const QModelIndex &index) | ||||
910 | { | ||||
911 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
912 | return; | ||||
913 | } | ||||
914 | | ||||
915 | NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); | ||||
916 | ri.closeWindowRequest(d->windows.at(index.row())); | ||||
917 | } | ||||
918 | | ||||
919 | void XWindowTasksModel::requestMove(const QModelIndex &index) | ||||
920 | { | ||||
921 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
922 | return; | ||||
923 | } | ||||
924 | | ||||
925 | const WId window = d->windows.at(index.row()); | ||||
926 | const KWindowInfo *info = d->windowInfo(window); | ||||
927 | | ||||
928 | bool onCurrent = info->isOnCurrentDesktop(); | ||||
929 | | ||||
930 | if (!onCurrent) { | ||||
931 | KWindowSystem::setCurrentDesktop(info->desktop()); | ||||
932 | KWindowSystem::forceActiveWindow(window); | ||||
933 | } | ||||
934 | | ||||
935 | if (info->isMinimized()) { | ||||
936 | KWindowSystem::unminimizeWindow(window); | ||||
937 | } | ||||
938 | | ||||
939 | const QRect &geom = info->geometry(); | ||||
940 | | ||||
941 | NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); | ||||
942 | ri.moveResizeRequest(window, geom.center().x(), geom.center().y(), NET::Move); | ||||
943 | } | ||||
944 | | ||||
945 | void XWindowTasksModel::requestResize(const QModelIndex &index) | ||||
946 | { | ||||
947 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
948 | return; | ||||
949 | } | ||||
950 | | ||||
951 | const WId window = d->windows.at(index.row()); | ||||
952 | const KWindowInfo *info = d->windowInfo(window); | ||||
953 | | ||||
954 | bool onCurrent = info->isOnCurrentDesktop(); | ||||
955 | | ||||
956 | if (!onCurrent) { | ||||
957 | KWindowSystem::setCurrentDesktop(info->desktop()); | ||||
958 | KWindowSystem::forceActiveWindow(window); | ||||
959 | } | ||||
960 | | ||||
961 | if (info->isMinimized()) { | ||||
962 | KWindowSystem::unminimizeWindow(window); | ||||
963 | } | ||||
964 | | ||||
965 | const QRect &geom = info->geometry(); | ||||
966 | | ||||
967 | NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); | ||||
968 | ri.moveResizeRequest(window, geom.bottomRight().x(), geom.bottomRight().y(), NET::BottomRight); | ||||
969 | } | ||||
970 | | ||||
971 | void XWindowTasksModel::requestToggleMinimized(const QModelIndex &index) | ||||
972 | { | ||||
973 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
974 | return; | ||||
975 | } | ||||
976 | | ||||
977 | const WId window = d->windows.at(index.row()); | ||||
978 | const KWindowInfo *info = d->windowInfo(window); | ||||
979 | | ||||
980 | if (info->isMinimized()) { | ||||
981 | bool onCurrent = info->isOnCurrentDesktop(); | ||||
982 | | ||||
983 | // FIXME: Move logic up into proxy? (See also others.) | ||||
984 | if (!onCurrent) { | ||||
985 | KWindowSystem::setCurrentDesktop(info->desktop()); | ||||
986 | } | ||||
987 | | ||||
988 | KWindowSystem::unminimizeWindow(window); | ||||
989 | | ||||
990 | if (onCurrent) { | ||||
991 | KWindowSystem::forceActiveWindow(window); | ||||
992 | } | ||||
993 | } else { | ||||
994 | KWindowSystem::minimizeWindow(window); | ||||
995 | } | ||||
996 | } | ||||
997 | | ||||
998 | void XWindowTasksModel::requestToggleMaximized(const QModelIndex &index) | ||||
999 | { | ||||
1000 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1001 | return; | ||||
1002 | } | ||||
1003 | | ||||
1004 | const WId window = d->windows.at(index.row()); | ||||
1005 | const KWindowInfo *info = d->windowInfo(window); | ||||
1006 | bool onCurrent = info->isOnCurrentDesktop(); | ||||
1007 | bool restore = (info->state() & NET::MaxHoriz) && (bool)(info->state() & NET::MaxVert); | ||||
1008 | | ||||
1009 | // FIXME: Move logic up into proxy? (See also others.) | ||||
1010 | if (!onCurrent) { | ||||
1011 | KWindowSystem::setCurrentDesktop(info->desktop()); | ||||
1012 | } | ||||
1013 | | ||||
1014 | if (info->isMinimized()) { | ||||
1015 | KWindowSystem::unminimizeWindow(window); | ||||
1016 | } | ||||
1017 | | ||||
1018 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, 0); | ||||
1019 | | ||||
1020 | if (restore) { | ||||
1021 | ni.setState(0, NET::Max); | ||||
1022 | } else { | ||||
1023 | ni.setState(NET::Max, NET::Max); | ||||
1024 | } | ||||
1025 | | ||||
1026 | if (!onCurrent) { | ||||
1027 | KWindowSystem::forceActiveWindow(window); | ||||
1028 | } | ||||
1029 | } | ||||
1030 | | ||||
1031 | void XWindowTasksModel::requestToggleKeepAbove(const QModelIndex &index) | ||||
1032 | { | ||||
1033 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1034 | return; | ||||
1035 | } | ||||
1036 | | ||||
1037 | const WId window = d->windows.at(index.row()); | ||||
1038 | const KWindowInfo *info = d->windowInfo(window); | ||||
1039 | | ||||
1040 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, 0); | ||||
1041 | | ||||
1042 | if (info->state() & NET::StaysOnTop) { | ||||
1043 | ni.setState(0, NET::StaysOnTop); | ||||
1044 | } else { | ||||
1045 | ni.setState(NET::StaysOnTop, NET::StaysOnTop); | ||||
1046 | } | ||||
1047 | } | ||||
1048 | | ||||
1049 | void XWindowTasksModel::requestToggleKeepBelow(const QModelIndex &index) | ||||
1050 | { | ||||
1051 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1052 | return; | ||||
1053 | } | ||||
1054 | | ||||
1055 | const WId window = d->windows.at(index.row()); | ||||
1056 | const KWindowInfo *info = d->windowInfo(window); | ||||
1057 | | ||||
1058 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, 0); | ||||
1059 | | ||||
1060 | if (info->state() & NET::KeepBelow) { | ||||
1061 | ni.setState(0, NET::KeepBelow); | ||||
1062 | } else { | ||||
1063 | ni.setState(NET::KeepBelow, NET::KeepBelow); | ||||
1064 | } | ||||
1065 | } | ||||
1066 | | ||||
1067 | void XWindowTasksModel::requestToggleFullScreen(const QModelIndex &index) | ||||
1068 | { | ||||
1069 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1070 | return; | ||||
1071 | } | ||||
1072 | | ||||
1073 | const WId window = d->windows.at(index.row()); | ||||
1074 | const KWindowInfo *info = d->windowInfo(window); | ||||
1075 | | ||||
1076 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, 0); | ||||
1077 | | ||||
1078 | if (info->state() & NET::FullScreen) { | ||||
1079 | ni.setState(0, NET::FullScreen); | ||||
1080 | } else { | ||||
1081 | ni.setState(NET::FullScreen, NET::FullScreen); | ||||
1082 | } | ||||
1083 | } | ||||
1084 | | ||||
1085 | void XWindowTasksModel::requestToggleShaded(const QModelIndex &index) | ||||
1086 | { | ||||
1087 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1088 | return; | ||||
1089 | } | ||||
1090 | | ||||
1091 | const WId window = d->windows.at(index.row()); | ||||
1092 | const KWindowInfo *info = d->windowInfo(window); | ||||
1093 | | ||||
1094 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, 0); | ||||
1095 | | ||||
1096 | if (info->state() & NET::Shaded) { | ||||
1097 | ni.setState(0, NET::Shaded); | ||||
1098 | } else { | ||||
1099 | ni.setState(NET::Shaded, NET::Shaded); | ||||
1100 | } | ||||
1101 | } | ||||
1102 | | ||||
1103 | void XWindowTasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) | ||||
1104 | { | ||||
1105 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1106 | return; | ||||
1107 | } | ||||
1108 | | ||||
1109 | const WId window = d->windows.at(index.row()); | ||||
1110 | const KWindowInfo *info = d->windowInfo(window); | ||||
1111 | | ||||
1112 | if (desktop == 0) { | ||||
1113 | if (info->onAllDesktops()) { | ||||
1114 | KWindowSystem::setOnDesktop(window, KWindowSystem::currentDesktop()); | ||||
1115 | KWindowSystem::forceActiveWindow(window); | ||||
1116 | } else { | ||||
1117 | KWindowSystem::setOnAllDesktops(window, true); | ||||
1118 | } | ||||
1119 | | ||||
1120 | return; | ||||
1121 | // FIXME Move add-new-desktop logic up into proxy. | ||||
1122 | } else if (desktop > KWindowSystem::numberOfDesktops()) { | ||||
1123 | desktop = KWindowSystem::numberOfDesktops() + 1; | ||||
1124 | | ||||
1125 | // FIXME Arbitrary limit of 20 copied from old code. | ||||
1126 | if (desktop > 20) { | ||||
1127 | return; | ||||
1128 | } | ||||
1129 | | ||||
1130 | NETRootInfo ri(QX11Info::connection(), NET::NumberOfDesktops); | ||||
1131 | ri.setNumberOfDesktops(desktop); | ||||
1132 | } | ||||
1133 | | ||||
1134 | KWindowSystem::setOnDesktop(window, desktop); | ||||
1135 | | ||||
1136 | if (desktop == KWindowSystem::currentDesktop()) { | ||||
1137 | KWindowSystem::forceActiveWindow(window); | ||||
1138 | } | ||||
1139 | } | ||||
1140 | | ||||
1141 | void XWindowTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate) | ||||
1142 | { | ||||
1143 | Q_UNUSED(delegate) | ||||
1144 | | ||||
1145 | if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { | ||||
1146 | return; | ||||
1147 | } | ||||
1148 | | ||||
1149 | const WId window = d->windows.at(index.row()); | ||||
1150 | | ||||
1151 | if (d->delegateGeometries.contains(window) | ||||
1152 | && d->delegateGeometries.value(window) == geometry) { | ||||
1153 | return; | ||||
1154 | } | ||||
1155 | | ||||
1156 | NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), 0, 0); | ||||
1157 | NETRect rect; | ||||
1158 | | ||||
1159 | if (geometry.isValid()) { | ||||
1160 | rect.pos.x = geometry.x(); | ||||
1161 | rect.pos.y = geometry.y(); | ||||
1162 | rect.size.width = geometry.width(); | ||||
1163 | rect.size.height = geometry.height(); | ||||
1164 | | ||||
1165 | d->delegateGeometries.insert(window, geometry); | ||||
1166 | } else { | ||||
1167 | d->delegateGeometries.remove(window); | ||||
1168 | } | ||||
1169 | | ||||
1170 | ni.setIconGeometry(rect); | ||||
1171 | } | ||||
1172 | | ||||
1173 | } |
windowInfoCache values leak in destructor