File indexing completed on 2024-05-19 05:35:55
0001 /* 0002 SPDX-FileCopyrightText: 2007 Daniel Laidig <d.laidig@gmx.de> 0003 SPDX-FileCopyrightText: 2012 Luís Gabriel Lima <lampih@gmail.com> 0004 SPDX-FileCopyrightText: 2016 Eike Hein <hein.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "pagermodel.h" 0010 #include "windowmodel.h" 0011 0012 #include <activityinfo.h> 0013 #include <virtualdesktopinfo.h> 0014 #include <waylandtasksmodel.h> 0015 #include <windowtasksmodel.h> 0016 #include <xwindowtasksmodel.h> 0017 0018 #include <QDBusConnection> 0019 #include <QDBusMessage> 0020 #include <QDBusPendingCall> 0021 #include <QGuiApplication> 0022 #include <QMetaEnum> 0023 #include <QUuid> 0024 #include <QScreen> 0025 0026 #include <KWindowSystem> 0027 0028 #if HAVE_X11 0029 #include <KX11Extras> 0030 #endif 0031 0032 #include <PlasmaActivities/Controller> 0033 0034 using namespace TaskManager; 0035 0036 class PagerModel::Private 0037 { 0038 public: 0039 Private(PagerModel *q); 0040 ~Private(); 0041 0042 static int instanceCount; 0043 0044 bool componentComplete = false; 0045 0046 PagerType pagerType = VirtualDesktops; 0047 bool enabled = false; 0048 bool showDesktop = false; 0049 0050 bool showOnlyCurrentScreen = false; 0051 QRect screenGeometry; 0052 QRect virtualGeometry; 0053 0054 WindowTasksModel *tasksModel = nullptr; 0055 0056 static ActivityInfo *activityInfo; 0057 QMetaObject::Connection activityNumberConn; 0058 QMetaObject::Connection activityNamesConn; 0059 0060 static VirtualDesktopInfo *virtualDesktopInfo; 0061 QMetaObject::Connection virtualDesktopNumberConn; 0062 QMetaObject::Connection virtualDesktopNamesConn; 0063 0064 QList<WindowModel *> windowModels; 0065 0066 void refreshDataSource(); 0067 0068 private: 0069 PagerModel *q; 0070 }; 0071 0072 int PagerModel::Private::instanceCount = 0; 0073 ActivityInfo *PagerModel::Private::activityInfo = nullptr; 0074 VirtualDesktopInfo *PagerModel::Private::virtualDesktopInfo = nullptr; 0075 0076 PagerModel::Private::Private(PagerModel *q) 0077 : q(q) 0078 { 0079 ++instanceCount; 0080 0081 if (!activityInfo) { 0082 activityInfo = new ActivityInfo(); 0083 } 0084 0085 QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, &PagerModel::shouldShowPagerChanged); 0086 0087 if (!virtualDesktopInfo) { 0088 virtualDesktopInfo = new VirtualDesktopInfo(); 0089 } 0090 0091 QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::numberOfDesktopsChanged, q, &PagerModel::shouldShowPagerChanged); 0092 0093 QObject::connect(activityInfo, &ActivityInfo::currentActivityChanged, q, [this]() { 0094 if (pagerType == VirtualDesktops && windowModels.count()) { 0095 for (auto windowModel : std::as_const(windowModels)) { 0096 windowModel->setActivity(activityInfo->currentActivity()); 0097 } 0098 } 0099 }); 0100 0101 QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::desktopLayoutRowsChanged, q, &PagerModel::layoutRowsChanged); 0102 } 0103 0104 PagerModel::Private::~Private() 0105 { 0106 --instanceCount; 0107 0108 if (!instanceCount) { 0109 delete activityInfo; 0110 activityInfo = nullptr; 0111 delete virtualDesktopInfo; 0112 virtualDesktopInfo = nullptr; 0113 } 0114 } 0115 0116 void PagerModel::Private::refreshDataSource() 0117 { 0118 if (pagerType == VirtualDesktops) { 0119 QObject::disconnect(virtualDesktopNumberConn); 0120 virtualDesktopNumberConn = QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::numberOfDesktopsChanged, q, [this]() { 0121 q->refresh(); 0122 }); 0123 0124 QObject::disconnect(virtualDesktopNamesConn); 0125 virtualDesktopNamesConn = QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::desktopNamesChanged, q, [this]() { 0126 if (q->rowCount()) { 0127 Q_EMIT q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), QList<int>{Qt::DisplayRole}); 0128 } 0129 }); 0130 0131 QObject::disconnect(activityNumberConn); 0132 QObject::disconnect(activityNamesConn); 0133 0134 QObject::disconnect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged); 0135 QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); 0136 } else { 0137 QObject::disconnect(activityNumberConn); 0138 activityNumberConn = QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, [this]() { 0139 q->refresh(); 0140 }); 0141 0142 QObject::disconnect(activityNamesConn); 0143 activityNamesConn = QObject::connect(activityInfo, &ActivityInfo::namesOfRunningActivitiesChanged, q, [this]() { 0144 q->refresh(); 0145 }); 0146 0147 QObject::disconnect(virtualDesktopNumberConn); 0148 QObject::disconnect(virtualDesktopNamesConn); 0149 0150 QObject::disconnect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged); 0151 QObject::connect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); 0152 } 0153 0154 Q_EMIT q->currentPageChanged(); 0155 } 0156 0157 PagerModel::PagerModel(QObject *parent) 0158 : QAbstractListModel(parent) 0159 , d(new Private(this)) 0160 { 0161 d->tasksModel = new WindowTasksModel(this); 0162 0163 computePagerItemSize(); 0164 const auto screens = qGuiApp->screens(); 0165 for (QScreen *screen : screens) { 0166 connect(screen, &QScreen::geometryChanged, this, &PagerModel::computePagerItemSize); 0167 } 0168 connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) { 0169 connect(screen, &QScreen::geometryChanged, this, &PagerModel::computePagerItemSize); 0170 computePagerItemSize(); 0171 }); 0172 connect(qGuiApp, &QGuiApplication::screenRemoved, this, &PagerModel::computePagerItemSize); 0173 } 0174 0175 PagerModel::~PagerModel() 0176 { 0177 } 0178 0179 QHash<int, QByteArray> PagerModel::roleNames() const 0180 { 0181 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); 0182 0183 QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); 0184 0185 for (int i = 0; i < e.keyCount(); ++i) { 0186 roles.insert(e.value(i), e.key(i)); 0187 } 0188 0189 return roles; 0190 } 0191 0192 int PagerModel::rowCount(const QModelIndex &parent) const 0193 { 0194 if (parent.isValid()) { 0195 return 0; 0196 } 0197 0198 return d->windowModels.count(); 0199 } 0200 0201 QVariant PagerModel::data(const QModelIndex &index, int role) const 0202 { 0203 if (!index.isValid() || index.row() < 0 || index.row() >= d->windowModels.count()) { 0204 return QVariant(); 0205 } 0206 0207 if (role == Qt::DisplayRole) { 0208 if (d->pagerType == VirtualDesktops) { 0209 return d->virtualDesktopInfo->desktopNames().at(index.row()); 0210 } else { 0211 QString activityId = d->activityInfo->runningActivities().at(index.row()); 0212 return d->activityInfo->activityName(activityId); 0213 } 0214 } else if (role == TasksModel) { 0215 return QVariant::fromValue(d->windowModels.at(index.row())); 0216 } 0217 0218 return QVariant(); 0219 } 0220 0221 PagerModel::PagerType PagerModel::pagerType() const 0222 { 0223 return d->pagerType; 0224 } 0225 0226 void PagerModel::setPagerType(PagerType type) 0227 { 0228 if (d->pagerType != type) { 0229 d->pagerType = type; 0230 0231 refresh(); 0232 0233 Q_EMIT pagerTypeChanged(); 0234 Q_EMIT shouldShowPagerChanged(); 0235 } 0236 } 0237 0238 bool PagerModel::enabled() const 0239 { 0240 return d->enabled; 0241 } 0242 0243 void PagerModel::setEnabled(bool enabled) 0244 { 0245 if (enabled && !d->enabled) { 0246 refresh(); 0247 d->enabled = true; 0248 Q_EMIT enabledChanged(); 0249 } else if (!enabled && d->enabled) { 0250 beginResetModel(); 0251 0252 disconnect(d->activityNumberConn); 0253 disconnect(d->activityNamesConn); 0254 disconnect(d->virtualDesktopNumberConn); 0255 disconnect(d->virtualDesktopNamesConn); 0256 0257 qDeleteAll(d->windowModels); 0258 d->windowModels.clear(); 0259 0260 endResetModel(); 0261 0262 d->enabled = false; 0263 Q_EMIT enabledChanged(); 0264 0265 Q_EMIT countChanged(); 0266 } 0267 } 0268 0269 bool PagerModel::shouldShowPager() const 0270 { 0271 return (d->pagerType == VirtualDesktops) ? d->virtualDesktopInfo->numberOfDesktops() > 1 : d->activityInfo->numberOfRunningActivities() > 1; 0272 } 0273 0274 bool PagerModel::showDesktop() const 0275 { 0276 return d->showDesktop; 0277 } 0278 0279 void PagerModel::setShowDesktop(bool show) 0280 { 0281 if (d->showDesktop != show) { 0282 d->showDesktop = show; 0283 0284 Q_EMIT showDesktopChanged(); 0285 } 0286 } 0287 0288 bool PagerModel::showOnlyCurrentScreen() const 0289 { 0290 return d->showOnlyCurrentScreen; 0291 } 0292 0293 void PagerModel::setShowOnlyCurrentScreen(bool show) 0294 { 0295 if (d->showOnlyCurrentScreen != show) { 0296 d->showOnlyCurrentScreen = show; 0297 0298 if (d->screenGeometry.isValid()) { 0299 Q_EMIT pagerItemSizeChanged(); 0300 0301 refresh(); 0302 } 0303 0304 Q_EMIT showOnlyCurrentScreenChanged(); 0305 } 0306 } 0307 0308 QRect PagerModel::screenGeometry() const 0309 { 0310 return d->screenGeometry; 0311 } 0312 0313 void PagerModel::setScreenGeometry(const QRect &geometry) 0314 { 0315 if (d->screenGeometry != geometry) { 0316 d->screenGeometry = geometry; 0317 0318 if (d->showOnlyCurrentScreen) { 0319 Q_EMIT pagerItemSizeChanged(); 0320 0321 refresh(); 0322 } 0323 0324 Q_EMIT showOnlyCurrentScreenChanged(); 0325 } 0326 } 0327 0328 int PagerModel::currentPage() const 0329 { 0330 if (d->pagerType == VirtualDesktops) { 0331 return d->virtualDesktopInfo->desktopIds().indexOf(d->virtualDesktopInfo->currentDesktop()); 0332 } else { 0333 return d->activityInfo->runningActivities().indexOf(d->activityInfo->currentActivity()); 0334 } 0335 } 0336 0337 int PagerModel::layoutRows() const 0338 { 0339 const int rows = std::min(d->virtualDesktopInfo->desktopLayoutRows(), d->virtualDesktopInfo->numberOfDesktops()); 0340 return std::max(1, rows); 0341 } 0342 0343 QSize PagerModel::pagerItemSize() const 0344 { 0345 if (d->showOnlyCurrentScreen && d->screenGeometry.isValid()) { 0346 #if HAVE_X11 0347 const double devicePixelRatio = KWindowSystem::isPlatformWayland() ? 1.0 : qGuiApp->devicePixelRatio(); 0348 #else 0349 constexpr int devicePixelRatio = 1; 0350 #endif 0351 return d->screenGeometry.size() * devicePixelRatio; 0352 } 0353 0354 return d->virtualGeometry.size(); 0355 } 0356 0357 QList<QVariant> PagerModel::stackingOrder(const QModelIndex &index) const 0358 { 0359 return index.data(TaskManager::AbstractTasksModel::StackingOrder).toList(); 0360 } 0361 0362 void PagerModel::refresh() 0363 { 0364 if (!d->componentComplete) { 0365 return; 0366 } 0367 0368 beginResetModel(); 0369 0370 d->refreshDataSource(); 0371 0372 int modelCount = d->windowModels.count(); 0373 const int modelsNeeded = ((d->pagerType == VirtualDesktops) ? d->virtualDesktopInfo->numberOfDesktops() : d->activityInfo->numberOfRunningActivities()); 0374 0375 if (modelCount > modelsNeeded) { 0376 while (modelCount != modelsNeeded) { 0377 delete d->windowModels.takeLast(); 0378 --modelCount; 0379 } 0380 } else if (modelsNeeded > modelCount) { 0381 while (modelCount != modelsNeeded) { 0382 WindowModel *windowModel = new WindowModel(this); 0383 windowModel->setFilterSkipPager(true); 0384 windowModel->setFilterByVirtualDesktop(true); 0385 windowModel->setFilterByActivity(true); 0386 windowModel->setDemandingAttentionSkipsFilters(false); 0387 windowModel->setSourceModel(d->tasksModel); 0388 d->windowModels.append(windowModel); 0389 ++modelCount; 0390 } 0391 } 0392 0393 if (d->pagerType == VirtualDesktops) { 0394 int virtualDesktop = 0; 0395 0396 for (auto windowModel : std::as_const(d->windowModels)) { 0397 windowModel->setVirtualDesktop(d->virtualDesktopInfo->desktopIds().at(virtualDesktop)); 0398 ++virtualDesktop; 0399 0400 windowModel->setActivity(d->activityInfo->currentActivity()); 0401 } 0402 } else { 0403 int activityIndex = 0; 0404 const QStringList &runningActivities = d->activityInfo->runningActivities(); 0405 0406 for (auto windowModel : std::as_const(d->windowModels)) { 0407 windowModel->setVirtualDesktop(); 0408 0409 windowModel->setActivity(runningActivities.at(activityIndex)); 0410 ++activityIndex; 0411 } 0412 } 0413 0414 for (auto windowModel : std::as_const(d->windowModels)) { 0415 if (d->showOnlyCurrentScreen && d->screenGeometry.isValid()) { 0416 windowModel->setScreenGeometry(d->screenGeometry); 0417 windowModel->setFilterByScreen(true); 0418 } else { 0419 windowModel->setFilterByScreen(false); 0420 } 0421 } 0422 0423 endResetModel(); 0424 0425 Q_EMIT countChanged(); 0426 } 0427 0428 void PagerModel::moveWindow(const QModelIndex &index, 0429 double x, 0430 double y, 0431 const QVariant &targetItemId, 0432 const QVariant &sourceItemId, 0433 qreal widthScaleFactor, 0434 qreal heightScaleFactor) 0435 { 0436 const auto taskModelIndex = static_cast<const WindowModel *>(index.model())->mapToSource(index); 0437 const bool isOnAllDesktops = index.data(TaskManager::AbstractTasksModel::IsOnAllVirtualDesktops).toBool(); 0438 0439 if (d->pagerType == VirtualDesktops) { 0440 if (!isOnAllDesktops) { 0441 d->tasksModel->requestVirtualDesktops(taskModelIndex, {targetItemId}); 0442 } 0443 } else { 0444 const QStringList &runningActivities = d->activityInfo->runningActivities(); 0445 const QString &newActivity = targetItemId.toString(); 0446 if (runningActivities.contains(newActivity)) { 0447 QStringList activities = index.data(TaskManager::AbstractTasksModel::Activities).toStringList(); 0448 if (!activities.contains(newActivity)) { 0449 activities.removeOne(sourceItemId.toString()); 0450 activities.append(newActivity); 0451 d->tasksModel->requestActivities(taskModelIndex, activities); 0452 } 0453 } 0454 } 0455 #if HAVE_X11 0456 if (KWindowSystem::isPlatformX11() && !index.data(TaskManager::AbstractTasksModel::IsFullScreen).toBool() 0457 && (targetItemId == sourceItemId || isOnAllDesktops)) { 0458 const auto winIds = index.data(TaskManager::AbstractTasksModel::WinIdList).toList(); 0459 if (winIds.isEmpty()) { 0460 return; 0461 } 0462 QPointF dest(x / widthScaleFactor, y / heightScaleFactor); 0463 0464 // Don't move windows to negative positions. 0465 dest = QPointF(qMax(dest.x(), qreal(0.0)), qMax(dest.y(), qreal(0.0))); 0466 0467 // Use _NET_MOVERESIZE_WINDOW rather than plain move, so that the WM knows this is a pager request. 0468 NETRootInfo info(QX11Info::connection(), NET::Properties()); 0469 const int flags = (0x20 << 12) | (0x03 << 8) | 1; // From tool, x/y, northwest gravity. 0470 const QPoint &d = dest.toPoint(); 0471 info.moveResizeWindowRequest(winIds[0].toUInt(), flags, d.x(), d.y(), 0, 0); 0472 } 0473 #endif 0474 } 0475 0476 void PagerModel::changePage(int page) 0477 { 0478 if (currentPage() == page) { 0479 if (d->showDesktop) { 0480 QDBusConnection::sessionBus().asyncCall(QDBusMessage::createMethodCall(QLatin1String("org.kde.plasmashell"), 0481 QLatin1String("/PlasmaShell"), 0482 QLatin1String("org.kde.PlasmaShell"), 0483 QLatin1String("toggleDashboard"))); 0484 } 0485 } else { 0486 if (d->pagerType == VirtualDesktops) { 0487 d->virtualDesktopInfo->requestActivate(d->virtualDesktopInfo->desktopIds().at(page)); 0488 } else { 0489 const QStringList &runningActivities = d->activityInfo->runningActivities(); 0490 if (page < runningActivities.length()) { 0491 KActivities::Controller activitiesController; 0492 activitiesController.setCurrentActivity(runningActivities.at(page)); 0493 } 0494 } 0495 } 0496 } 0497 0498 void PagerModel::drop(QMimeData *mimeData, int modifiers, const QVariant &itemId) 0499 { 0500 if (!mimeData) { 0501 return; 0502 } 0503 0504 auto findWindows = [this](const auto &windowIds) -> QList<QModelIndex> { 0505 QList<QModelIndex> indices; 0506 for (const auto &id : windowIds) { 0507 for (int i = 0; i < d->tasksModel->rowCount(); ++i) { 0508 const QModelIndex &idx = d->tasksModel->index(i, 0); 0509 const QVariantList &winIds = idx.data(TaskManager::AbstractTasksModel::WinIdList).toList(); 0510 if (!winIds.isEmpty() && winIds.at(0).value<typename std::remove_reference_t<decltype(windowIds)>::value_type>() == id) { 0511 indices.push_back(idx); 0512 break; 0513 } 0514 } 0515 } 0516 return indices; 0517 }; 0518 0519 bool ok = false; 0520 QList<QModelIndex> indices; 0521 if (KWindowSystem::isPlatformX11()) { 0522 indices = findWindows(TaskManager::XWindowTasksModel::winIdsFromMimeData(mimeData, &ok)); 0523 } else if (KWindowSystem::isPlatformWayland()) { 0524 indices = findWindows(TaskManager::WaylandTasksModel::winIdsFromMimeData(mimeData, &ok)); 0525 } 0526 if (!ok) { 0527 return; 0528 } 0529 0530 for (const auto &index : std::as_const(indices)) { 0531 if (d->pagerType == VirtualDesktops) { 0532 if (!index.data(TaskManager::AbstractTasksModel::IsOnAllVirtualDesktops).toBool()) { 0533 d->tasksModel->requestVirtualDesktops(index, {itemId}); 0534 } 0535 } else { 0536 QString newActivity = itemId.toString(); 0537 const QStringList &runningActivities = d->activityInfo->runningActivities(); 0538 0539 if (!runningActivities.contains(newActivity)) { 0540 return; 0541 } 0542 0543 QStringList activities = index.data(TaskManager::AbstractTasksModel::Activities).toStringList(); 0544 0545 if (modifiers & Qt::ControlModifier) { // 'copy' => add to activity 0546 if (!activities.contains(newActivity)) { 0547 activities << newActivity; 0548 } 0549 } else { // 'move' to activity 0550 // if on only one activity, set it to only the new activity 0551 // if on >1 activity, remove it from the current activity and add it to the new activity 0552 const QString currentActivity = d->activityInfo->currentActivity(); 0553 activities.removeAll(currentActivity); 0554 activities << newActivity; 0555 } 0556 d->tasksModel->requestActivities(index, activities); 0557 } 0558 } 0559 } 0560 0561 void PagerModel::addDesktop() 0562 { 0563 d->virtualDesktopInfo->requestCreateDesktop(d->virtualDesktopInfo->numberOfDesktops()); 0564 } 0565 0566 void PagerModel::removeDesktop() 0567 { 0568 if (d->virtualDesktopInfo->numberOfDesktops() == 1) { 0569 return; 0570 } 0571 0572 d->virtualDesktopInfo->requestRemoveDesktop(d->virtualDesktopInfo->numberOfDesktops() - 1); 0573 } 0574 0575 void PagerModel::classBegin() 0576 { 0577 } 0578 0579 void PagerModel::componentComplete() 0580 { 0581 d->componentComplete = true; 0582 0583 if (d->enabled) { 0584 refresh(); 0585 } 0586 } 0587 0588 void PagerModel::computePagerItemSize() 0589 { 0590 #if HAVE_X11 0591 const double devicePixelRatio = KWindowSystem::isPlatformWayland() ? 1.0 : qGuiApp->devicePixelRatio(); 0592 #else 0593 constexpr int devicePixelRatio = 1; 0594 #endif 0595 QRect wholeScreen; 0596 for (const auto screens = qGuiApp->screens(); auto screen : screens) { 0597 const QRect geometry = screen->geometry(); 0598 wholeScreen |= QRect(geometry.topLeft(), geometry.size() * devicePixelRatio); 0599 } 0600 0601 if (d->virtualGeometry != wholeScreen) { 0602 d->virtualGeometry = wholeScreen; 0603 Q_EMIT pagerItemSizeChanged(); 0604 } 0605 } 0606 0607 #include "moc_pagermodel.cpp"