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