File indexing completed on 2024-05-05 17:44:56
0001 /* 0002 SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org> 0003 SPDX-FileCopyrightText: 2008 Aaron J. Seigo <aseigo@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "xwindowtasksmodel.h" 0009 #include "tasktools.h" 0010 #include "xwindowsystemeventbatcher.h" 0011 0012 #include <KDesktopFile> 0013 #include <KDirWatch> 0014 #include <KIconLoader> 0015 #include <KService> 0016 #include <KSharedConfig> 0017 #include <KSycoca> 0018 #include <KWindowInfo> 0019 #include <KX11Extras> 0020 0021 #include <QBuffer> 0022 #include <QDir> 0023 #include <QFile> 0024 #include <QIcon> 0025 #include <QSet> 0026 #include <QTimer> 0027 #include <QUrlQuery> 0028 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0029 #include <private/qtx11extras_p.h> 0030 #else 0031 #include <QX11Info> 0032 #endif 0033 #include <chrono> 0034 0035 using namespace std::chrono_literals; 0036 0037 namespace TaskManager 0038 { 0039 static const NET::Properties windowInfoFlags = 0040 NET::WMState | NET::XAWMState | NET::WMDesktop | NET::WMVisibleName | NET::WMGeometry | NET::WMFrameExtents | NET::WMWindowType | NET::WMPid; 0041 static const NET::Properties2 windowInfoFlags2 = NET::WM2DesktopFileName | NET::WM2Activities | NET::WM2WindowClass | NET::WM2AllowedActions 0042 | NET::WM2AppMenuObjectPath | NET::WM2AppMenuServiceName | NET::WM2GTKApplicationId; 0043 0044 class Q_DECL_HIDDEN XWindowTasksModel::Private 0045 { 0046 public: 0047 Private(XWindowTasksModel *q); 0048 ~Private(); 0049 0050 QVector<WId> windows; 0051 0052 // key=transient child, value=leader 0053 QHash<WId, WId> transients; 0054 // key=leader, values=transient children 0055 QMultiHash<WId, WId> transientsDemandingAttention; 0056 0057 QHash<WId, KWindowInfo *> windowInfoCache; 0058 QHash<WId, AppData> appDataCache; 0059 QHash<WId, QRect> delegateGeometries; 0060 QSet<WId> usingFallbackIcon; 0061 QHash<WId, QTime> lastActivated; 0062 QList<WId> cachedStackingOrder; 0063 WId activeWindow = -1; 0064 KSharedConfig::Ptr rulesConfig; 0065 KDirWatch *configWatcher = nullptr; 0066 QTimer sycocaChangeTimer; 0067 0068 void init(); 0069 void addWindow(WId window); 0070 void removeWindow(WId window); 0071 void windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2); 0072 void transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2); 0073 void dataChanged(WId window, const QVector<int> &roles); 0074 0075 KWindowInfo *windowInfo(WId window); 0076 AppData appData(WId window); 0077 QString appMenuServiceName(WId window); 0078 QString appMenuObjectPath(WId window); 0079 0080 QIcon icon(WId window); 0081 static QString mimeType(); 0082 static QString groupMimeType(); 0083 QUrl windowUrl(WId window); 0084 QUrl launcherUrl(WId window, bool encodeFallbackIcon = true); 0085 bool demandsAttention(WId window); 0086 0087 private: 0088 XWindowTasksModel *q; 0089 }; 0090 0091 XWindowTasksModel::Private::Private(XWindowTasksModel *q) 0092 : q(q) 0093 { 0094 } 0095 0096 XWindowTasksModel::Private::~Private() 0097 { 0098 qDeleteAll(windowInfoCache); 0099 windowInfoCache.clear(); 0100 } 0101 0102 void XWindowTasksModel::Private::init() 0103 { 0104 auto clearCacheAndRefresh = [this] { 0105 if (!windows.count()) { 0106 return; 0107 } 0108 0109 appDataCache.clear(); 0110 0111 // Emit changes of all roles satisfied from app data cache. 0112 Q_EMIT q->dataChanged(q->index(0, 0), 0113 q->index(windows.count() - 1, 0), 0114 QVector<int>{Qt::DecorationRole, 0115 AbstractTasksModel::AppId, 0116 AbstractTasksModel::AppName, 0117 AbstractTasksModel::GenericName, 0118 AbstractTasksModel::LauncherUrl, 0119 AbstractTasksModel::LauncherUrlWithoutIcon, 0120 AbstractTasksModel::CanLaunchNewInstance, 0121 AbstractTasksModel::SkipTaskbar}); 0122 }; 0123 0124 cachedStackingOrder = KX11Extras::stackingOrder(); 0125 0126 sycocaChangeTimer.setSingleShot(true); 0127 sycocaChangeTimer.setInterval(100ms); 0128 0129 QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, clearCacheAndRefresh); 0130 0131 QObject::connect(KSycoca::self(), &KSycoca::databaseChanged, q, [this]() { 0132 sycocaChangeTimer.start(); 0133 }); 0134 0135 rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); 0136 configWatcher = new KDirWatch(q); 0137 0138 foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) { 0139 configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc")); 0140 } 0141 0142 auto rulesConfigChange = [this, clearCacheAndRefresh] { 0143 rulesConfig->reparseConfiguration(); 0144 clearCacheAndRefresh(); 0145 }; 0146 0147 QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange); 0148 QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange); 0149 QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange); 0150 0151 auto windowSystem = new XWindowSystemEventBatcher(q); 0152 0153 QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowAdded, q, [this](WId window) { 0154 addWindow(window); 0155 }); 0156 0157 QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowRemoved, q, [this](WId window) { 0158 removeWindow(window); 0159 }); 0160 0161 QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowChanged, q, [this](WId window, NET::Properties properties, NET::Properties2 properties2) { 0162 windowChanged(window, properties, properties2); 0163 }); 0164 0165 // Update IsActive for previously- and newly-active windows. 0166 QObject::connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, q, [this](WId window) { 0167 const WId oldActiveWindow = activeWindow; 0168 0169 const auto leader = transients.value(window, XCB_WINDOW_NONE); 0170 if (leader != XCB_WINDOW_NONE) { 0171 window = leader; 0172 } 0173 0174 activeWindow = window; 0175 lastActivated[activeWindow] = QTime::currentTime(); 0176 0177 int row = windows.indexOf(oldActiveWindow); 0178 0179 if (row != -1) { 0180 dataChanged(oldActiveWindow, QVector<int>{IsActive}); 0181 } 0182 0183 row = windows.indexOf(window); 0184 0185 if (row != -1) { 0186 dataChanged(window, QVector<int>{IsActive}); 0187 } 0188 }); 0189 0190 QObject::connect(KX11Extras::self(), &KX11Extras::stackingOrderChanged, q, [this]() { 0191 // No need to do anything if the model is empty. This avoids calling q->dataChanged with an invalid QModelIndex. 0192 if (q->rowCount() == 0) { 0193 return; 0194 } 0195 cachedStackingOrder = KX11Extras::stackingOrder(); 0196 Q_ASSERT(q->hasIndex(0, 0)); 0197 Q_ASSERT(q->hasIndex(q->rowCount() - 1, 0)); 0198 Q_EMIT q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), QVector<int>{StackingOrder}); 0199 }); 0200 0201 activeWindow = KX11Extras::activeWindow(); 0202 0203 // Add existing windows. 0204 foreach (const WId window, KX11Extras::windows()) { 0205 addWindow(window); 0206 } 0207 } 0208 0209 void XWindowTasksModel::Private::addWindow(WId window) 0210 { 0211 // Don't add window twice. 0212 if (windows.contains(window)) { 0213 return; 0214 } 0215 0216 KWindowInfo info(window, NET::WMWindowType | NET::WMState | NET::WMName | NET::WMVisibleName, NET::WM2TransientFor); 0217 0218 NET::WindowType wType = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask 0219 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | NET::NotificationMask); 0220 0221 const WId leader = info.transientFor(); 0222 0223 // Handle transient. 0224 if (leader > 0 && leader != window && leader != QX11Info::appRootWindow() && !transients.contains(window) && windows.contains(leader)) { 0225 transients.insert(window, leader); 0226 0227 // Update demands attention state for leader. 0228 if (info.hasState(NET::DemandsAttention) && windows.contains(leader)) { 0229 transientsDemandingAttention.insert(leader, window); 0230 dataChanged(leader, QVector<int>{IsDemandingAttention}); 0231 } 0232 0233 return; 0234 } 0235 0236 // Ignore NET::Tool and other special window types; they are not considered tasks. 0237 if (wType != NET::Normal && wType != NET::Override && wType != NET::Unknown && wType != NET::Dialog && wType != NET::Utility) { 0238 return; 0239 } 0240 0241 const int count = windows.count(); 0242 q->beginInsertRows(QModelIndex(), count, count); 0243 windows.append(window); 0244 q->endInsertRows(); 0245 } 0246 0247 void XWindowTasksModel::Private::removeWindow(WId window) 0248 { 0249 const int row = windows.indexOf(window); 0250 0251 if (row != -1) { 0252 q->beginRemoveRows(QModelIndex(), row, row); 0253 windows.removeAt(row); 0254 transientsDemandingAttention.remove(window); 0255 delete windowInfoCache.take(window); 0256 appDataCache.remove(window); 0257 delegateGeometries.remove(window); 0258 usingFallbackIcon.remove(window); 0259 lastActivated.remove(window); 0260 q->endRemoveRows(); 0261 } else { // Could be a transient. 0262 // Removing a transient might change the demands attention state of the leader. 0263 if (transients.remove(window)) { 0264 const WId leader = transientsDemandingAttention.key(window, XCB_WINDOW_NONE); 0265 0266 if (leader != XCB_WINDOW_NONE) { 0267 transientsDemandingAttention.remove(leader, window); 0268 dataChanged(leader, QVector<int>{IsDemandingAttention}); 0269 } 0270 } 0271 } 0272 0273 if (activeWindow == window) { 0274 activeWindow = -1; 0275 } 0276 } 0277 0278 void XWindowTasksModel::Private::transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2) 0279 { 0280 // Changes to a transient's state might change demands attention state for leader. 0281 if (properties & (NET::WMState | NET::XAWMState)) { 0282 const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor); 0283 const WId leader = info.transientFor(); 0284 0285 if (!windows.contains(leader)) { 0286 return; 0287 } 0288 0289 if (info.hasState(NET::DemandsAttention)) { 0290 if (!transientsDemandingAttention.values(leader).contains(window)) { 0291 transientsDemandingAttention.insert(leader, window); 0292 dataChanged(leader, QVector<int>{IsDemandingAttention}); 0293 } 0294 } else if (transientsDemandingAttention.remove(window)) { 0295 dataChanged(leader, QVector<int>{IsDemandingAttention}); 0296 } 0297 // Leader might have changed. 0298 } else if (properties2 & NET::WM2TransientFor) { 0299 const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor); 0300 0301 if (info.hasState(NET::DemandsAttention)) { 0302 const WId oldLeader = transientsDemandingAttention.key(window, XCB_WINDOW_NONE); 0303 0304 if (oldLeader != XCB_WINDOW_NONE) { 0305 const WId leader = info.transientFor(); 0306 0307 if (leader != oldLeader) { 0308 transientsDemandingAttention.remove(oldLeader, window); 0309 transientsDemandingAttention.insert(leader, window); 0310 dataChanged(oldLeader, QVector<int>{IsDemandingAttention}); 0311 dataChanged(leader, QVector<int>{IsDemandingAttention}); 0312 } 0313 } 0314 } 0315 } 0316 } 0317 0318 void XWindowTasksModel::Private::windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2) 0319 { 0320 if (transients.contains(window)) { 0321 transientChanged(window, properties, properties2); 0322 return; 0323 } 0324 0325 bool wipeInfoCache = false; 0326 bool wipeAppDataCache = false; 0327 QVector<int> changedRoles; 0328 0329 if (properties & (NET::WMPid) || properties2 & (NET::WM2DesktopFileName | NET::WM2WindowClass)) { 0330 wipeInfoCache = true; 0331 wipeAppDataCache = true; 0332 changedRoles << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl << AppPid << SkipTaskbar << CanLaunchNewInstance; 0333 } 0334 0335 if (properties & (NET::WMName | NET::WMVisibleName)) { 0336 changedRoles << Qt::DisplayRole; 0337 wipeInfoCache = true; 0338 } 0339 0340 if ((properties & NET::WMIcon) && usingFallbackIcon.contains(window)) { 0341 wipeAppDataCache = true; 0342 0343 if (!changedRoles.contains(Qt::DecorationRole)) { 0344 changedRoles << Qt::DecorationRole; 0345 } 0346 } 0347 0348 // FIXME TODO: It might be worth keeping track of which windows were demanding 0349 // attention (or not) to avoid emitting this role on every state change, as 0350 // TaskGroupingProxyModel needs to check for group-ability when a change to it 0351 // is announced and the queried state is false. 0352 if (properties & (NET::WMState | NET::XAWMState)) { 0353 wipeInfoCache = true; 0354 changedRoles << IsFullScreen << IsMaximized << IsMinimized << IsKeepAbove << IsKeepBelow; 0355 changedRoles << IsShaded << IsDemandingAttention << SkipTaskbar << SkipPager; 0356 } 0357 0358 if (properties & NET::WMWindowType) { 0359 wipeInfoCache = true; 0360 changedRoles << SkipTaskbar; 0361 } 0362 0363 if (properties2 & NET::WM2AllowedActions) { 0364 wipeInfoCache = true; 0365 changedRoles << IsClosable << IsMovable << IsResizable << IsMaximizable << IsMinimizable; 0366 changedRoles << IsFullScreenable << IsShadeable << IsVirtualDesktopsChangeable; 0367 } 0368 0369 if (properties & NET::WMDesktop) { 0370 wipeInfoCache = true; 0371 changedRoles << VirtualDesktops << IsOnAllVirtualDesktops; 0372 } 0373 0374 if (properties & NET::WMGeometry) { 0375 wipeInfoCache = true; 0376 changedRoles << Geometry << ScreenGeometry; 0377 } 0378 0379 if (properties2 & NET::WM2Activities) { 0380 wipeInfoCache = true; 0381 changedRoles << Activities; 0382 } 0383 0384 if (properties2 & NET::WM2AppMenuServiceName) { 0385 wipeInfoCache = true; 0386 changedRoles << ApplicationMenuServiceName; 0387 } 0388 0389 if (properties2 & NET::WM2AppMenuObjectPath) { 0390 wipeInfoCache = true; 0391 changedRoles << ApplicationMenuObjectPath; 0392 } 0393 0394 if (wipeInfoCache) { 0395 delete windowInfoCache.take(window); 0396 } 0397 0398 if (wipeAppDataCache) { 0399 appDataCache.remove(window); 0400 usingFallbackIcon.remove(window); 0401 } 0402 0403 if (!changedRoles.isEmpty()) { 0404 dataChanged(window, changedRoles); 0405 } 0406 } 0407 0408 void XWindowTasksModel::Private::dataChanged(WId window, const QVector<int> &roles) 0409 { 0410 const int i = windows.indexOf(window); 0411 0412 if (i == -1) { 0413 return; 0414 } 0415 0416 QModelIndex idx = q->index(i); 0417 Q_EMIT q->dataChanged(idx, idx, roles); 0418 } 0419 0420 KWindowInfo *XWindowTasksModel::Private::windowInfo(WId window) 0421 { 0422 const auto &it = windowInfoCache.constFind(window); 0423 0424 if (it != windowInfoCache.constEnd()) { 0425 return *it; 0426 } 0427 0428 KWindowInfo *info = new KWindowInfo(window, windowInfoFlags, windowInfoFlags2); 0429 windowInfoCache.insert(window, info); 0430 0431 return info; 0432 } 0433 0434 AppData XWindowTasksModel::Private::appData(WId window) 0435 { 0436 const auto &it = appDataCache.constFind(window); 0437 0438 if (it != appDataCache.constEnd()) { 0439 return *it; 0440 } 0441 0442 const AppData &data = appDataFromUrl(windowUrl(window)); 0443 0444 // If we weren't able to derive a launcher URL from the window meta data, 0445 // fall back to WM_CLASS Class string as app id. This helps with apps we 0446 // can't map to an URL due to existing outside the regular system 0447 // environment, e.g. wine clients. 0448 if (data.id.isEmpty() && data.url.isEmpty()) { 0449 AppData dataCopy = data; 0450 0451 dataCopy.id = windowInfo(window)->windowClassClass(); 0452 0453 appDataCache.insert(window, dataCopy); 0454 0455 return dataCopy; 0456 } 0457 0458 appDataCache.insert(window, data); 0459 0460 return data; 0461 } 0462 0463 QString XWindowTasksModel::Private::appMenuServiceName(WId window) 0464 { 0465 const KWindowInfo *info = windowInfo(window); 0466 return QString::fromUtf8(info->applicationMenuServiceName()); 0467 } 0468 0469 QString XWindowTasksModel::Private::appMenuObjectPath(WId window) 0470 { 0471 const KWindowInfo *info = windowInfo(window); 0472 return QString::fromUtf8(info->applicationMenuObjectPath()); 0473 } 0474 0475 QIcon XWindowTasksModel::Private::icon(WId window) 0476 { 0477 const AppData &app = appData(window); 0478 0479 if (!app.icon.isNull()) { 0480 return app.icon; 0481 } 0482 0483 QIcon icon; 0484 0485 icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeSmall, KIconLoader::SizeSmall, false)); 0486 icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium, false)); 0487 icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeMedium, KIconLoader::SizeMedium, false)); 0488 icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false)); 0489 0490 appDataCache[window].icon = icon; 0491 usingFallbackIcon.insert(window); 0492 0493 return icon; 0494 } 0495 0496 QString XWindowTasksModel::Private::mimeType() 0497 { 0498 return QStringLiteral("windowsystem/winid"); 0499 } 0500 0501 QString XWindowTasksModel::Private::groupMimeType() 0502 { 0503 return QStringLiteral("windowsystem/multiple-winids"); 0504 } 0505 0506 QUrl XWindowTasksModel::Private::windowUrl(WId window) 0507 { 0508 const KWindowInfo *info = windowInfo(window); 0509 0510 QString desktopFile = QString::fromUtf8(info->desktopFileName()); 0511 0512 if (desktopFile.isEmpty()) { 0513 desktopFile = QString::fromUtf8(info->gtkApplicationId()); 0514 } 0515 0516 if (!desktopFile.isEmpty()) { 0517 KService::Ptr service = KService::serviceByStorageId(desktopFile); 0518 0519 if (service) { 0520 const QString &menuId = service->menuId(); 0521 0522 // applications: URLs are used to refer to applications by their KService::menuId 0523 // (i.e. .desktop file name) rather than the absolute path to a .desktop file. 0524 if (!menuId.isEmpty()) { 0525 return QUrl(QStringLiteral("applications:") + menuId); 0526 } 0527 0528 return QUrl::fromLocalFile(service->entryPath()); 0529 } 0530 0531 if (!desktopFile.endsWith(QLatin1String(".desktop"))) { 0532 desktopFile.append(QLatin1String(".desktop")); 0533 } 0534 0535 if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) { 0536 return QUrl::fromLocalFile(desktopFile); 0537 } 0538 } 0539 0540 return windowUrlFromMetadata(info->windowClassClass(), info->pid(), rulesConfig, info->windowClassName()); 0541 } 0542 0543 QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon) 0544 { 0545 const AppData &data = appData(window); 0546 0547 QUrl url = data.url; 0548 if (!encodeFallbackIcon || !data.icon.name().isEmpty()) { 0549 return url; 0550 } 0551 0552 // Forego adding the window icon pixmap if the URL is otherwise empty. 0553 if (!url.isValid()) { 0554 return QUrl(); 0555 } 0556 0557 // Only serialize pixmap data if the window pixmap is actually being used. 0558 // QIcon::name() used above only returns a themed icon name but nothing when 0559 // the icon was created using an absolute path, as can be the case with, e.g. 0560 // containerized apps. 0561 if (!usingFallbackIcon.contains(window)) { 0562 return url; 0563 } 0564 0565 QPixmap pixmap; 0566 0567 if (!data.icon.isNull()) { 0568 pixmap = data.icon.pixmap(KIconLoader::SizeLarge); 0569 } 0570 0571 if (pixmap.isNull()) { 0572 pixmap = KX11Extras::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false); 0573 } 0574 0575 if (pixmap.isNull()) { 0576 return data.url; 0577 } 0578 QUrlQuery uQuery(url); 0579 QByteArray bytes; 0580 QBuffer buffer(&bytes); 0581 buffer.open(QIODevice::WriteOnly); 0582 pixmap.save(&buffer, "PNG"); 0583 uQuery.addQueryItem(QStringLiteral("iconData"), bytes.toBase64(QByteArray::Base64UrlEncoding)); 0584 0585 url.setQuery(uQuery); 0586 0587 return url; 0588 } 0589 0590 bool XWindowTasksModel::Private::demandsAttention(WId window) 0591 { 0592 if (windows.contains(window)) { 0593 return ((windowInfo(window)->hasState(NET::DemandsAttention)) || transientsDemandingAttention.contains(window)); 0594 } 0595 0596 return false; 0597 } 0598 0599 XWindowTasksModel::XWindowTasksModel(QObject *parent) 0600 : AbstractWindowTasksModel(parent) 0601 , d(new Private(this)) 0602 { 0603 d->init(); 0604 } 0605 0606 XWindowTasksModel::~XWindowTasksModel() 0607 { 0608 } 0609 0610 QVariant XWindowTasksModel::data(const QModelIndex &index, int role) const 0611 { 0612 if (!index.isValid() || index.row() >= d->windows.count()) { 0613 return QVariant(); 0614 } 0615 0616 const WId window = d->windows.at(index.row()); 0617 0618 if (role == Qt::DisplayRole) { 0619 return d->windowInfo(window)->visibleName(); 0620 } else if (role == Qt::DecorationRole) { 0621 return d->icon(window); 0622 } else if (role == AppId) { 0623 return d->appData(window).id; 0624 } else if (role == AppName) { 0625 return d->appData(window).name; 0626 } else if (role == GenericName) { 0627 return d->appData(window).genericName; 0628 } else if (role == LauncherUrl) { 0629 return d->launcherUrl(window); 0630 } else if (role == LauncherUrlWithoutIcon) { 0631 return d->launcherUrl(window, false /* encodeFallbackIcon */); 0632 } else if (role == WinIdList) { 0633 return QVariantList() << window; 0634 } else if (role == MimeType) { 0635 return d->mimeType(); 0636 } else if (role == MimeData) { 0637 return QByteArray(reinterpret_cast<const char *>(&window), sizeof(window)); 0638 } else if (role == IsWindow) { 0639 return true; 0640 } else if (role == IsActive) { 0641 return (window == d->activeWindow); 0642 } else if (role == IsClosable) { 0643 return d->windowInfo(window)->actionSupported(NET::ActionClose); 0644 } else if (role == IsMovable) { 0645 return d->windowInfo(window)->actionSupported(NET::ActionMove); 0646 } else if (role == IsResizable) { 0647 return d->windowInfo(window)->actionSupported(NET::ActionResize); 0648 } else if (role == IsMaximizable) { 0649 return d->windowInfo(window)->actionSupported(NET::ActionMax); 0650 } else if (role == IsMaximized) { 0651 const KWindowInfo *info = d->windowInfo(window); 0652 return info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert); 0653 } else if (role == IsMinimizable) { 0654 return d->windowInfo(window)->actionSupported(NET::ActionMinimize); 0655 } else if (role == IsMinimized) { 0656 return d->windowInfo(window)->isMinimized(); 0657 } else if (role == IsHidden) { 0658 return d->windowInfo(window)->hasState(NET::Hidden); 0659 } else if (role == IsKeepAbove) { 0660 return d->windowInfo(window)->hasState(NET::KeepAbove); 0661 } else if (role == IsKeepBelow) { 0662 return d->windowInfo(window)->hasState(NET::KeepBelow); 0663 } else if (role == IsFullScreenable) { 0664 return d->windowInfo(window)->actionSupported(NET::ActionFullScreen); 0665 } else if (role == IsFullScreen) { 0666 return d->windowInfo(window)->hasState(NET::FullScreen); 0667 } else if (role == IsShadeable) { 0668 return d->windowInfo(window)->actionSupported(NET::ActionShade); 0669 } else if (role == IsShaded) { 0670 return d->windowInfo(window)->hasState(NET::Shaded); 0671 } else if (role == IsVirtualDesktopsChangeable) { 0672 return d->windowInfo(window)->actionSupported(NET::ActionChangeDesktop); 0673 } else if (role == VirtualDesktops) { 0674 return QVariantList() << d->windowInfo(window)->desktop(); 0675 } else if (role == IsOnAllVirtualDesktops) { 0676 return d->windowInfo(window)->onAllDesktops(); 0677 } else if (role == Geometry) { 0678 return d->windowInfo(window)->frameGeometry(); 0679 } else if (role == ScreenGeometry) { 0680 return screenGeometry(d->windowInfo(window)->frameGeometry().center()); 0681 } else if (role == Activities) { 0682 return d->windowInfo(window)->activities(); 0683 } else if (role == IsDemandingAttention) { 0684 return d->demandsAttention(window); 0685 } else if (role == SkipTaskbar) { 0686 const KWindowInfo *info = d->windowInfo(window); 0687 // _NET_WM_WINDOW_TYPE_UTILITY type windows should not be on task bars, 0688 // but they should be shown on pagers. 0689 return (info->hasState(NET::SkipTaskbar) || info->windowType(NET::UtilityMask) == NET::Utility || d->appData(window).skipTaskbar); 0690 } else if (role == SkipPager) { 0691 return d->windowInfo(window)->hasState(NET::SkipPager); 0692 } else if (role == AppPid) { 0693 return d->windowInfo(window)->pid(); 0694 } else if (role == StackingOrder) { 0695 return d->cachedStackingOrder.indexOf(window); 0696 } else if (role == LastActivated) { 0697 if (d->lastActivated.contains(window)) { 0698 return d->lastActivated.value(window); 0699 } 0700 } else if (role == ApplicationMenuObjectPath) { 0701 return d->appMenuObjectPath(window); 0702 } else if (role == ApplicationMenuServiceName) { 0703 return d->appMenuServiceName(window); 0704 } else if (role == CanLaunchNewInstance) { 0705 return canLauchNewInstance(d->appData(window)); 0706 } 0707 0708 return QVariant(); 0709 } 0710 0711 int XWindowTasksModel::rowCount(const QModelIndex &parent) const 0712 { 0713 return parent.isValid() ? 0 : d->windows.count(); 0714 } 0715 0716 void XWindowTasksModel::requestActivate(const QModelIndex &index) 0717 { 0718 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0719 return; 0720 } 0721 0722 if (index.row() >= 0 && index.row() < d->windows.count()) { 0723 WId window = d->windows.at(index.row()); 0724 0725 // Pull forward any transient demanding attention. 0726 if (d->transientsDemandingAttention.contains(window)) { 0727 window = d->transientsDemandingAttention.value(window); 0728 // Quote from legacy libtaskmanager: 0729 // "this is a work around for (at least?) kwin where a shaded transient will prevent the main 0730 // window from being brought forward unless the transient is actually pulled forward, most 0731 // easily reproduced by opening a modal file open/save dialog on an app then shading the file 0732 // dialog and trying to bring the window forward by clicking on it in a tasks widget 0733 // TODO: do we need to check all the transients for shaded?" 0734 } else if (!d->transients.isEmpty()) { 0735 const auto transients = d->transients.keys(window); 0736 for (const auto transient : qAsConst(transients)) { 0737 KWindowInfo info(transient, NET::WMState, NET::WM2TransientFor); 0738 if (info.valid(true) && info.hasState(NET::Shaded)) { 0739 window = transient; 0740 break; 0741 } 0742 } 0743 } 0744 0745 KX11Extras::forceActiveWindow(window); 0746 } 0747 } 0748 0749 void XWindowTasksModel::requestNewInstance(const QModelIndex &index) 0750 { 0751 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0752 return; 0753 } 0754 0755 runApp(d->appData(d->windows.at(index.row()))); 0756 } 0757 0758 void XWindowTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls) 0759 { 0760 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count() || urls.isEmpty()) { 0761 return; 0762 } 0763 0764 runApp(d->appData(d->windows.at(index.row())), urls); 0765 } 0766 0767 void XWindowTasksModel::requestClose(const QModelIndex &index) 0768 { 0769 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0770 return; 0771 } 0772 0773 NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); 0774 ri.closeWindowRequest(d->windows.at(index.row())); 0775 } 0776 0777 void XWindowTasksModel::requestMove(const QModelIndex &index) 0778 { 0779 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0780 return; 0781 } 0782 0783 const WId window = d->windows.at(index.row()); 0784 const KWindowInfo *info = d->windowInfo(window); 0785 0786 bool onCurrent = info->isOnCurrentDesktop(); 0787 0788 if (!onCurrent) { 0789 KX11Extras::setCurrentDesktop(info->desktop()); 0790 KX11Extras::forceActiveWindow(window); 0791 } 0792 0793 if (info->isMinimized()) { 0794 KX11Extras::unminimizeWindow(window); 0795 } 0796 0797 const QRect &geom = info->geometry(); 0798 0799 NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); 0800 ri.moveResizeRequest(window, geom.center().x(), geom.center().y(), NET::Move); 0801 } 0802 0803 void XWindowTasksModel::requestResize(const QModelIndex &index) 0804 { 0805 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0806 return; 0807 } 0808 0809 const WId window = d->windows.at(index.row()); 0810 const KWindowInfo *info = d->windowInfo(window); 0811 0812 bool onCurrent = info->isOnCurrentDesktop(); 0813 0814 if (!onCurrent) { 0815 KX11Extras::setCurrentDesktop(info->desktop()); 0816 KX11Extras::forceActiveWindow(window); 0817 } 0818 0819 if (info->isMinimized()) { 0820 KX11Extras::unminimizeWindow(window); 0821 } 0822 0823 const QRect &geom = info->geometry(); 0824 0825 NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); 0826 ri.moveResizeRequest(window, geom.bottomRight().x(), geom.bottomRight().y(), NET::BottomRight); 0827 } 0828 0829 void XWindowTasksModel::requestToggleMinimized(const QModelIndex &index) 0830 { 0831 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0832 return; 0833 } 0834 0835 const WId window = d->windows.at(index.row()); 0836 const KWindowInfo *info = d->windowInfo(window); 0837 0838 if (index.data(AbstractTasksModel::IsHidden).toBool()) { 0839 bool onCurrent = info->isOnCurrentDesktop(); 0840 0841 // FIXME: Move logic up into proxy? (See also others.) 0842 if (!onCurrent) { 0843 KX11Extras::setCurrentDesktop(info->desktop()); 0844 } 0845 0846 KX11Extras::unminimizeWindow(window); 0847 0848 if (onCurrent) { 0849 KX11Extras::forceActiveWindow(window); 0850 } 0851 } else { 0852 KX11Extras::minimizeWindow(window); 0853 } 0854 } 0855 0856 void XWindowTasksModel::requestToggleMaximized(const QModelIndex &index) 0857 { 0858 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0859 return; 0860 } 0861 0862 const WId window = d->windows.at(index.row()); 0863 const KWindowInfo *info = d->windowInfo(window); 0864 bool onCurrent = info->isOnCurrentDesktop(); 0865 bool restore = (info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert)); 0866 0867 // FIXME: Move logic up into proxy? (See also others.) 0868 if (!onCurrent) { 0869 KX11Extras::setCurrentDesktop(info->desktop()); 0870 } 0871 0872 if (info->isMinimized()) { 0873 KX11Extras::unminimizeWindow(window); 0874 } 0875 0876 NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0877 0878 if (restore) { 0879 ni.setState(NET::States(), NET::Max); 0880 } else { 0881 ni.setState(NET::Max, NET::Max); 0882 } 0883 0884 if (!onCurrent) { 0885 KX11Extras::forceActiveWindow(window); 0886 } 0887 } 0888 0889 void XWindowTasksModel::requestToggleKeepAbove(const QModelIndex &index) 0890 { 0891 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0892 return; 0893 } 0894 0895 const WId window = d->windows.at(index.row()); 0896 const KWindowInfo *info = d->windowInfo(window); 0897 0898 NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0899 0900 if (info->hasState(NET::KeepAbove)) { 0901 ni.setState(NET::States(), NET::KeepAbove); 0902 } else { 0903 ni.setState(NET::KeepAbove, NET::KeepAbove); 0904 } 0905 } 0906 0907 void XWindowTasksModel::requestToggleKeepBelow(const QModelIndex &index) 0908 { 0909 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0910 return; 0911 } 0912 0913 const WId window = d->windows.at(index.row()); 0914 const KWindowInfo *info = d->windowInfo(window); 0915 0916 NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0917 0918 if (info->hasState(NET::KeepBelow)) { 0919 ni.setState(NET::States(), NET::KeepBelow); 0920 } else { 0921 ni.setState(NET::KeepBelow, NET::KeepBelow); 0922 } 0923 } 0924 0925 void XWindowTasksModel::requestToggleFullScreen(const QModelIndex &index) 0926 { 0927 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0928 return; 0929 } 0930 0931 const WId window = d->windows.at(index.row()); 0932 const KWindowInfo *info = d->windowInfo(window); 0933 0934 NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0935 0936 if (info->hasState(NET::FullScreen)) { 0937 ni.setState(NET::States(), NET::FullScreen); 0938 } else { 0939 ni.setState(NET::FullScreen, NET::FullScreen); 0940 } 0941 } 0942 0943 void XWindowTasksModel::requestToggleShaded(const QModelIndex &index) 0944 { 0945 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0946 return; 0947 } 0948 0949 const WId window = d->windows.at(index.row()); 0950 const KWindowInfo *info = d->windowInfo(window); 0951 0952 NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); 0953 0954 if (info->hasState(NET::Shaded)) { 0955 ni.setState(NET::States(), NET::Shaded); 0956 } else { 0957 ni.setState(NET::Shaded, NET::Shaded); 0958 } 0959 } 0960 0961 void XWindowTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) 0962 { 0963 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 0964 return; 0965 } 0966 0967 int desktop = 0; 0968 0969 if (!desktops.isEmpty()) { 0970 bool ok = false; 0971 0972 desktop = desktops.first().toUInt(&ok); 0973 0974 if (!ok) { 0975 return; 0976 } 0977 } 0978 0979 if (desktop > KX11Extras::numberOfDesktops()) { 0980 return; 0981 } 0982 0983 const WId window = d->windows.at(index.row()); 0984 const KWindowInfo *info = d->windowInfo(window); 0985 0986 if (desktop == 0) { 0987 if (info->onAllDesktops()) { 0988 KX11Extras::setOnDesktop(window, KX11Extras::currentDesktop()); 0989 KX11Extras::forceActiveWindow(window); 0990 } else { 0991 KX11Extras::setOnAllDesktops(window, true); 0992 } 0993 0994 return; 0995 } 0996 0997 KX11Extras::setOnDesktop(window, desktop); 0998 0999 if (desktop == KX11Extras::currentDesktop()) { 1000 KX11Extras::forceActiveWindow(window); 1001 } 1002 } 1003 1004 void XWindowTasksModel::requestNewVirtualDesktop(const QModelIndex &index) 1005 { 1006 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 1007 return; 1008 } 1009 1010 const WId window = d->windows.at(index.row()); 1011 const int desktop = KX11Extras::numberOfDesktops() + 1; 1012 1013 // FIXME Arbitrary limit of 20 copied from old code. 1014 if (desktop > 20) { 1015 return; 1016 } 1017 1018 NETRootInfo ri(QX11Info::connection(), NET::NumberOfDesktops); 1019 ri.setNumberOfDesktops(desktop); 1020 1021 KX11Extras::setOnDesktop(window, desktop); 1022 } 1023 1024 void XWindowTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities) 1025 { 1026 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 1027 return; 1028 } 1029 1030 const WId window = d->windows.at(index.row()); 1031 1032 KX11Extras::setOnActivities(window, activities); 1033 } 1034 1035 void XWindowTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate) 1036 { 1037 Q_UNUSED(delegate) 1038 1039 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { 1040 return; 1041 } 1042 1043 const WId window = d->windows.at(index.row()); 1044 1045 if (d->delegateGeometries.contains(window) && d->delegateGeometries.value(window) == geometry) { 1046 return; 1047 } 1048 1049 NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); 1050 NETRect rect; 1051 1052 if (geometry.isValid()) { 1053 rect.pos.x = geometry.x(); 1054 rect.pos.y = geometry.y(); 1055 rect.size.width = geometry.width(); 1056 rect.size.height = geometry.height(); 1057 1058 d->delegateGeometries.insert(window, geometry); 1059 } else { 1060 d->delegateGeometries.remove(window); 1061 } 1062 1063 ni.setIconGeometry(rect); 1064 } 1065 1066 WId XWindowTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok) 1067 { 1068 Q_ASSERT(mimeData); 1069 1070 if (ok) { 1071 *ok = false; 1072 } 1073 1074 if (!mimeData->hasFormat(Private::mimeType())) { 1075 return 0; 1076 } 1077 1078 QByteArray data(mimeData->data(Private::mimeType())); 1079 WId id; 1080 if (data.size() != sizeof(WId)) { 1081 #if QT_VERSION < QT_VERSION_CHECK(6, 4, 2) 1082 // Workaround for https://bugreports.qt.io/browse/QTBUG-71922 1083 QString idString = QString::fromUtf8(data); 1084 if (idString.startsWith(QLatin1String("strnum-"))) { 1085 id = QString::fromUtf8(data).mid(7).toUInt(); 1086 } else { 1087 return 0; 1088 } 1089 #else 1090 return 0; 1091 #endif 1092 } else { 1093 memcpy(&id, data.data(), sizeof(WId)); 1094 } 1095 1096 if (ok) { 1097 *ok = true; 1098 } 1099 1100 return id; 1101 } 1102 1103 QList<WId> XWindowTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok) 1104 { 1105 Q_ASSERT(mimeData); 1106 QList<WId> ids; 1107 1108 if (ok) { 1109 *ok = false; 1110 } 1111 1112 if (!mimeData->hasFormat(Private::groupMimeType())) { 1113 // Try to extract single window id. 1114 bool singularOk; 1115 WId id = winIdFromMimeData(mimeData, &singularOk); 1116 1117 if (ok) { 1118 *ok = singularOk; 1119 } 1120 1121 if (singularOk) { 1122 ids << id; 1123 } 1124 1125 return ids; 1126 } 1127 1128 QByteArray data(mimeData->data(Private::groupMimeType())); 1129 if ((unsigned int)data.size() < sizeof(int) + sizeof(WId)) { 1130 return ids; 1131 } 1132 1133 int count = 0; 1134 memcpy(&count, data.data(), sizeof(int)); 1135 if (count < 1 || (unsigned int)data.size() < sizeof(int) + sizeof(WId) * count) { 1136 return ids; 1137 } 1138 1139 WId id; 1140 for (int i = 0; i < count; ++i) { 1141 memcpy(&id, data.data() + sizeof(int) + sizeof(WId) * i, sizeof(WId)); 1142 ids << id; 1143 } 1144 1145 if (ok) { 1146 *ok = true; 1147 } 1148 1149 return ids; 1150 } 1151 1152 }