File indexing completed on 2024-11-10 04:57:21
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"