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"