File indexing completed on 2024-05-12 05:32:14

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2009 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "tabboxhandler.h"
0010 
0011 #include <config-kwin.h>
0012 
0013 // own
0014 #include "clientmodel.h"
0015 #include "scripting/scripting.h"
0016 #include "switcheritem.h"
0017 #include "tabbox_logging.h"
0018 #include "window.h"
0019 // Qt
0020 #include <QKeyEvent>
0021 #include <QQmlComponent>
0022 #include <QQmlContext>
0023 #include <QQmlEngine>
0024 #include <QQuickItem>
0025 #include <QQuickWindow>
0026 #include <QStandardPaths>
0027 #include <QTimer>
0028 #include <qpa/qwindowsysteminterface.h>
0029 // KDE
0030 #include <KLocalizedString>
0031 #include <KPackage/Package>
0032 #include <KPackage/PackageLoader>
0033 #include <KProcess>
0034 
0035 namespace KWin
0036 {
0037 namespace TabBox
0038 {
0039 
0040 class TabBoxHandlerPrivate
0041 {
0042 public:
0043     TabBoxHandlerPrivate(TabBoxHandler *q);
0044 
0045     ~TabBoxHandlerPrivate();
0046 
0047     /**
0048      * Updates the current highlight window state
0049      */
0050     void updateHighlightWindows();
0051     /**
0052      * Ends window highlighting
0053      */
0054     void endHighlightWindows(bool abort = false);
0055 
0056     void show();
0057     QQuickWindow *window() const;
0058     SwitcherItem *switcherItem() const;
0059 
0060     ClientModel *clientModel() const;
0061 
0062     bool isHighlightWindows() const;
0063 
0064     TabBoxHandler *q; // public pointer
0065     // members
0066     TabBoxConfig config;
0067     std::unique_ptr<QQmlContext> m_qmlContext;
0068     std::unique_ptr<QQmlComponent> m_qmlComponent;
0069     QObject *m_mainItem;
0070     QMap<QString, QObject *> m_clientTabBoxes;
0071     ClientModel *m_clientModel;
0072     QModelIndex index;
0073     /**
0074      * Indicates if the tabbox is shown.
0075      */
0076     bool isShown;
0077     Window *lastRaisedClient, *lastRaisedClientSucc;
0078     int wheelAngleDelta = 0;
0079 
0080 private:
0081     QObject *createSwitcherItem();
0082 };
0083 
0084 TabBoxHandlerPrivate::TabBoxHandlerPrivate(TabBoxHandler *q)
0085     : m_qmlContext()
0086     , m_qmlComponent(nullptr)
0087     , m_mainItem(nullptr)
0088 {
0089     this->q = q;
0090     isShown = false;
0091     lastRaisedClient = nullptr;
0092     lastRaisedClientSucc = nullptr;
0093     config = TabBoxConfig();
0094     m_clientModel = new ClientModel(q);
0095 }
0096 
0097 TabBoxHandlerPrivate::~TabBoxHandlerPrivate()
0098 {
0099     qDeleteAll(m_clientTabBoxes);
0100 }
0101 
0102 QQuickWindow *TabBoxHandlerPrivate::window() const
0103 {
0104     if (!m_mainItem) {
0105         return nullptr;
0106     }
0107     if (QQuickWindow *w = qobject_cast<QQuickWindow *>(m_mainItem)) {
0108         return w;
0109     }
0110     return m_mainItem->findChild<QQuickWindow *>();
0111 }
0112 
0113 #ifndef KWIN_UNIT_TEST
0114 SwitcherItem *TabBoxHandlerPrivate::switcherItem() const
0115 {
0116     if (!m_mainItem) {
0117         return nullptr;
0118     }
0119     if (SwitcherItem *i = qobject_cast<SwitcherItem *>(m_mainItem)) {
0120         return i;
0121     } else if (QQuickWindow *w = qobject_cast<QQuickWindow *>(m_mainItem)) {
0122         return w->contentItem()->findChild<SwitcherItem *>();
0123     }
0124     return m_mainItem->findChild<SwitcherItem *>();
0125 }
0126 #endif
0127 
0128 ClientModel *TabBoxHandlerPrivate::clientModel() const
0129 {
0130     return m_clientModel;
0131 }
0132 
0133 bool TabBoxHandlerPrivate::isHighlightWindows() const
0134 {
0135     const QQuickWindow *w = window();
0136     if (w && w->visibility() == QWindow::FullScreen) {
0137         return false;
0138     }
0139     return config.isHighlightWindows();
0140 }
0141 
0142 void TabBoxHandlerPrivate::updateHighlightWindows()
0143 {
0144     if (!isShown) {
0145         return;
0146     }
0147 
0148     Window *currentClient = q->client(index);
0149     QWindow *w = window();
0150 
0151     if (q->isKWinCompositing()) {
0152         if (lastRaisedClient) {
0153             q->elevateClient(lastRaisedClient, w, false);
0154         }
0155         lastRaisedClient = currentClient;
0156         // don't elevate desktop
0157         const auto desktop = q->desktopClient();
0158         if (currentClient && (!desktop || currentClient->internalId() != desktop->internalId())) {
0159             q->elevateClient(currentClient, w, true);
0160         }
0161     } else {
0162         if (lastRaisedClient) {
0163             q->shadeClient(lastRaisedClient, true);
0164             if (lastRaisedClientSucc) {
0165                 q->restack(lastRaisedClient, lastRaisedClientSucc);
0166             }
0167             // TODO lastRaisedClient->setMinimized( lastRaisedClientWasMinimized );
0168         }
0169 
0170         lastRaisedClient = currentClient;
0171         if (lastRaisedClient) {
0172             q->shadeClient(lastRaisedClient, false);
0173             // TODO if ( (lastRaisedClientWasMinimized = lastRaisedClient->isMinimized()) )
0174             //         lastRaisedClient->setMinimized( false );
0175             QList<Window *> order = q->stackingOrder();
0176             int succIdx = order.count() + 1;
0177             for (int i = 0; i < order.count(); ++i) {
0178                 if (order.at(i) == lastRaisedClient) {
0179                     succIdx = i + 1;
0180                     break;
0181                 }
0182             }
0183             lastRaisedClientSucc = (succIdx < order.count()) ? order.at(succIdx) : nullptr;
0184             q->raiseClient(lastRaisedClient);
0185         }
0186     }
0187 
0188     if (config.isShowTabBox() && w) {
0189         q->highlightWindows(currentClient, w);
0190     } else {
0191         q->highlightWindows(currentClient);
0192     }
0193 }
0194 
0195 void TabBoxHandlerPrivate::endHighlightWindows(bool abort)
0196 {
0197     Window *currentClient = q->client(index);
0198     if (isHighlightWindows() && q->isKWinCompositing()) {
0199         const auto stackingOrder = q->stackingOrder();
0200         for (Window *window : stackingOrder) {
0201             if (window != currentClient) { // to not mess up with wanted ShadeActive/ShadeHover state
0202                 q->shadeClient(window, true);
0203             }
0204         }
0205     }
0206     QWindow *w = window();
0207     if (currentClient) {
0208         q->elevateClient(currentClient, w, false);
0209     }
0210     if (abort && lastRaisedClient && lastRaisedClientSucc) {
0211         q->restack(lastRaisedClient, lastRaisedClientSucc);
0212     }
0213     lastRaisedClient = nullptr;
0214     lastRaisedClientSucc = nullptr;
0215     // highlight windows
0216     q->highlightWindows();
0217 }
0218 
0219 #ifndef KWIN_UNIT_TEST
0220 QObject *TabBoxHandlerPrivate::createSwitcherItem()
0221 {
0222     // first try look'n'feel package
0223     QString file = QStandardPaths::locate(
0224         QStandardPaths::GenericDataLocation,
0225         QStringLiteral("plasma/look-and-feel/%1/contents/windowswitcher/WindowSwitcher.qml").arg(config.layoutName()));
0226     if (file.isNull()) {
0227         const QString type = QStringLiteral("KWin/WindowSwitcher");
0228 
0229         KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(type, config.layoutName());
0230 
0231         if (!pkg.isValid()) {
0232             // load default
0233             qCWarning(KWIN_TABBOX) << "Could not load window switcher package" << config.layoutName() << ". Falling back to default";
0234             pkg = KPackage::PackageLoader::self()->loadPackage(type, TabBoxConfig::defaultLayoutName());
0235         }
0236 
0237         file = pkg.filePath("mainscript");
0238     }
0239     if (file.isNull()) {
0240         qCDebug(KWIN_TABBOX) << "Could not find QML file for window switcher";
0241         return nullptr;
0242     }
0243     m_qmlComponent->loadUrl(QUrl::fromLocalFile(file));
0244     if (m_qmlComponent->isError()) {
0245         qCWarning(KWIN_TABBOX) << "Component failed to load: " << m_qmlComponent->errors();
0246         QStringList args;
0247         args << QStringLiteral("--passivepopup") << i18n("The Window Switcher installation is broken, resources are missing.\n"
0248                                                          "Contact your distribution about this.")
0249              << QStringLiteral("20");
0250         KProcess::startDetached(QStringLiteral("kdialog"), args);
0251         m_qmlComponent.reset(nullptr);
0252     } else {
0253         QObject *object = m_qmlComponent->create(m_qmlContext.get());
0254         m_clientTabBoxes.insert(config.layoutName(), object);
0255         return object;
0256     }
0257     return nullptr;
0258 }
0259 #endif
0260 
0261 void TabBoxHandlerPrivate::show()
0262 {
0263 #ifndef KWIN_UNIT_TEST
0264     if (!m_qmlContext) {
0265         qmlRegisterType<SwitcherItem>("org.kde.kwin", 3, 0, "TabBoxSwitcher");
0266         m_qmlContext = std::make_unique<QQmlContext>(Scripting::self()->qmlEngine());
0267     }
0268     if (!m_qmlComponent) {
0269         m_qmlComponent = std::make_unique<QQmlComponent>(Scripting::self()->qmlEngine());
0270     }
0271     auto findMainItem = [this](const QMap<QString, QObject *> &tabBoxes) -> QObject * {
0272         auto it = tabBoxes.constFind(config.layoutName());
0273         if (it != tabBoxes.constEnd()) {
0274             return it.value();
0275         }
0276         return nullptr;
0277     };
0278     m_mainItem = nullptr;
0279     m_mainItem = findMainItem(m_clientTabBoxes);
0280     if (!m_mainItem) {
0281         m_mainItem = createSwitcherItem();
0282         if (!m_mainItem) {
0283             return;
0284         }
0285     }
0286     if (SwitcherItem *item = switcherItem()) {
0287         // In case the model isn't yet set (see below), index will be reset and therefore we
0288         // need to save the current index row (https://bugs.kde.org/show_bug.cgi?id=333511).
0289         int indexRow = index.row();
0290         if (!item->model()) {
0291             item->setModel(clientModel());
0292         }
0293         item->setAllDesktops(config.clientDesktopMode() == TabBoxConfig::AllDesktopsClients);
0294         item->setCurrentIndex(indexRow);
0295         item->setNoModifierGrab(q->noModifierGrab());
0296         Q_EMIT item->aboutToShow();
0297 
0298         // When SwitcherItem gets hidden, destroy also the window and main item
0299         QObject::connect(item, &SwitcherItem::visibleChanged, q, [this, item]() {
0300             if (!item->isVisible()) {
0301                 if (QQuickWindow *w = window()) {
0302                     w->hide();
0303                     w->destroy();
0304                 }
0305                 m_mainItem = nullptr;
0306             }
0307         });
0308 
0309         // everything is prepared, so let's make the whole thing visible
0310         item->setVisible(true);
0311     }
0312     if (QWindow *w = window()) {
0313         wheelAngleDelta = 0;
0314         w->installEventFilter(q);
0315         // pretend to activate the window to enable accessibility notifications
0316 #if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
0317         QWindowSystemInterface::handleWindowActivated(w, Qt::TabFocusReason);
0318 #else
0319         QWindowSystemInterface::handleFocusWindowChanged(w, Qt::TabFocusReason);
0320 #endif
0321     }
0322 #endif
0323 }
0324 
0325 /***********************************************
0326  * TabBoxHandler
0327  ***********************************************/
0328 
0329 TabBoxHandler::TabBoxHandler(QObject *parent)
0330     : QObject(parent)
0331 {
0332     KWin::TabBox::tabBox = this;
0333     d = new TabBoxHandlerPrivate(this);
0334 }
0335 
0336 TabBoxHandler::~TabBoxHandler()
0337 {
0338     delete d;
0339 }
0340 
0341 const KWin::TabBox::TabBoxConfig &TabBoxHandler::config() const
0342 {
0343     return d->config;
0344 }
0345 
0346 void TabBoxHandler::setConfig(const TabBoxConfig &config)
0347 {
0348     d->config = config;
0349     Q_EMIT configChanged();
0350 }
0351 
0352 void TabBoxHandler::show()
0353 {
0354     d->isShown = true;
0355     d->lastRaisedClient = nullptr;
0356     d->lastRaisedClientSucc = nullptr;
0357     if (d->config.isShowTabBox()) {
0358         d->show();
0359     }
0360     if (d->isHighlightWindows()) {
0361         // TODO this should be
0362         // QMetaObject::invokeMethod(this, "initHighlightWindows", Qt::QueuedConnection);
0363         // but we somehow need to cross > 1 event cycle (likely because of queued invocation in the effects)
0364         // to ensure the EffectWindow is present when updateHighlightWindows, thus elevating the window/tabbox
0365         QTimer::singleShot(1, this, &TabBoxHandler::initHighlightWindows);
0366     }
0367 }
0368 
0369 void TabBoxHandler::initHighlightWindows()
0370 {
0371     if (isKWinCompositing()) {
0372         const auto stack = stackingOrder();
0373         for (Window *window : stack) {
0374             shadeClient(window, false);
0375         }
0376     }
0377     d->updateHighlightWindows();
0378 }
0379 
0380 void TabBoxHandler::hide(bool abort)
0381 {
0382     d->isShown = false;
0383     if (d->isHighlightWindows()) {
0384         d->endHighlightWindows(abort);
0385     }
0386 #ifndef KWIN_UNIT_TEST
0387     if (SwitcherItem *item = d->switcherItem()) {
0388         Q_EMIT item->aboutToHide();
0389         if (item->automaticallyHide()) {
0390             item->setVisible(false);
0391         }
0392     }
0393 #endif
0394 }
0395 
0396 QModelIndex TabBoxHandler::nextPrev(bool forward) const
0397 {
0398     QModelIndex ret;
0399     QAbstractItemModel *model = d->clientModel();
0400     if (forward) {
0401         int column = d->index.column() + 1;
0402         int row = d->index.row();
0403         if (column == model->columnCount()) {
0404             column = 0;
0405             row++;
0406             if (row == model->rowCount()) {
0407                 row = 0;
0408             }
0409         }
0410         ret = model->index(row, column);
0411         if (!ret.isValid()) {
0412             ret = model->index(0, 0);
0413         }
0414     } else {
0415         int column = d->index.column() - 1;
0416         int row = d->index.row();
0417         if (column < 0) {
0418             column = model->columnCount() - 1;
0419             row--;
0420             if (row < 0) {
0421                 row = model->rowCount() - 1;
0422             }
0423         }
0424         ret = model->index(row, column);
0425         if (!ret.isValid()) {
0426             row = model->rowCount() - 1;
0427             for (int i = model->columnCount() - 1; i >= 0; i--) {
0428                 ret = model->index(row, i);
0429                 if (ret.isValid()) {
0430                     break;
0431                 }
0432             }
0433         }
0434     }
0435     if (ret.isValid()) {
0436         return ret;
0437     } else {
0438         return d->index;
0439     }
0440 }
0441 
0442 void TabBoxHandler::setCurrentIndex(const QModelIndex &index)
0443 {
0444     if (d->index == index) {
0445         return;
0446     }
0447     if (!index.isValid()) {
0448         return;
0449     }
0450     d->index = index;
0451     if (d->isHighlightWindows()) {
0452         d->updateHighlightWindows();
0453     }
0454     Q_EMIT selectedIndexChanged();
0455 }
0456 
0457 const QModelIndex &TabBoxHandler::currentIndex() const
0458 {
0459     return d->index;
0460 }
0461 
0462 void TabBoxHandler::grabbedKeyEvent(QKeyEvent *event) const
0463 {
0464     if (!d->m_mainItem || !d->window()) {
0465         return;
0466     }
0467     QCoreApplication::sendEvent(d->window(), event);
0468 }
0469 
0470 bool TabBoxHandler::containsPos(const QPoint &pos) const
0471 {
0472     if (!d->m_mainItem) {
0473         return false;
0474     }
0475     QWindow *w = d->window();
0476     if (w) {
0477         return w->geometry().contains(pos);
0478     }
0479     return false;
0480 }
0481 
0482 QModelIndex TabBoxHandler::index(Window *client) const
0483 {
0484     return d->clientModel()->index(client);
0485 }
0486 
0487 QList<Window *> TabBoxHandler::clientList() const
0488 {
0489     return d->clientModel()->clientList();
0490 }
0491 
0492 Window *TabBoxHandler::client(const QModelIndex &index) const
0493 {
0494     if (!index.isValid()) {
0495         return nullptr;
0496     }
0497     Window *c = static_cast<Window *>(
0498         d->clientModel()->data(index, ClientModel::ClientRole).value<void *>());
0499     return c;
0500 }
0501 
0502 void TabBoxHandler::createModel(bool partialReset)
0503 {
0504     d->clientModel()->createClientList(partialReset);
0505     // TODO: C++11 use lambda function
0506     bool lastRaised = false;
0507     bool lastRaisedSucc = false;
0508     const auto clients = stackingOrder();
0509     for (Window *window : clients) {
0510         if (window == d->lastRaisedClient) {
0511             lastRaised = true;
0512         }
0513         if (window == d->lastRaisedClientSucc) {
0514             lastRaisedSucc = true;
0515         }
0516     }
0517     if (d->lastRaisedClient && !lastRaised) {
0518         d->lastRaisedClient = nullptr;
0519     }
0520     if (d->lastRaisedClientSucc && !lastRaisedSucc) {
0521         d->lastRaisedClientSucc = nullptr;
0522     }
0523 }
0524 
0525 QModelIndex TabBoxHandler::first() const
0526 {
0527     return d->clientModel()->index(0, 0);
0528 }
0529 
0530 bool TabBoxHandler::eventFilter(QObject *watched, QEvent *e)
0531 {
0532     if (e->type() == QEvent::Wheel && watched == d->window()) {
0533         QWheelEvent *event = static_cast<QWheelEvent *>(e);
0534         // On x11 the delta for vertical scrolling might also be on X for whatever reason
0535         const int delta = std::abs(event->angleDelta().x()) > std::abs(event->angleDelta().y()) ? event->angleDelta().x() : event->angleDelta().y();
0536         d->wheelAngleDelta += delta;
0537         while (d->wheelAngleDelta <= -120) {
0538             d->wheelAngleDelta += 120;
0539             const QModelIndex index = nextPrev(true);
0540             if (index.isValid()) {
0541                 setCurrentIndex(index);
0542             }
0543         }
0544         while (d->wheelAngleDelta >= 120) {
0545             d->wheelAngleDelta -= 120;
0546             const QModelIndex index = nextPrev(false);
0547             if (index.isValid()) {
0548                 setCurrentIndex(index);
0549             }
0550         }
0551         return true;
0552     }
0553     // pass on
0554     return QObject::eventFilter(watched, e);
0555 }
0556 
0557 TabBoxHandler *tabBox = nullptr;
0558 
0559 } // namespace TabBox
0560 } // namespace KWin
0561 
0562 #include "moc_tabboxhandler.cpp"