File indexing completed on 2024-11-10 04:56:45
0001 /* 0002 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "effect/quickeffect.h" 0008 #include "core/output.h" 0009 #include "effect/effecthandler.h" 0010 0011 #include "logging_p.h" 0012 0013 #include <QQmlContext> 0014 #include <QQmlEngine> 0015 #include <QQmlIncubator> 0016 #include <QQuickItem> 0017 #include <QQuickWindow> 0018 0019 namespace KWin 0020 { 0021 0022 static QHash<QQuickWindow *, QuickSceneView *> s_views; 0023 0024 class QuickSceneViewIncubator : public QQmlIncubator 0025 { 0026 public: 0027 QuickSceneViewIncubator(QuickSceneEffect *effect, Output *screen, const std::function<void(QuickSceneViewIncubator *)> &statusChangedCallback) 0028 : QQmlIncubator(QQmlIncubator::Asynchronous) 0029 , m_effect(effect) 0030 , m_screen(screen) 0031 , m_statusChangedCallback(statusChangedCallback) 0032 { 0033 } 0034 0035 std::unique_ptr<QuickSceneView> result() 0036 { 0037 return std::move(m_view); 0038 } 0039 0040 void setInitialState(QObject *object) override 0041 { 0042 m_view = std::make_unique<QuickSceneView>(m_effect, m_screen); 0043 m_view->setAutomaticRepaint(false); 0044 m_view->setRootItem(qobject_cast<QQuickItem *>(object)); 0045 } 0046 0047 void statusChanged(QQmlIncubator::Status status) override 0048 { 0049 m_statusChangedCallback(this); 0050 } 0051 0052 private: 0053 QuickSceneEffect *m_effect; 0054 Output *m_screen; 0055 std::function<void(QuickSceneViewIncubator *)> m_statusChangedCallback; 0056 std::unique_ptr<QuickSceneView> m_view; 0057 }; 0058 0059 class QuickSceneEffectPrivate 0060 { 0061 public: 0062 static QuickSceneEffectPrivate *get(QuickSceneEffect *effect) 0063 { 0064 return effect->d.get(); 0065 } 0066 bool isItemOnScreen(QQuickItem *item, Output *screen) const; 0067 0068 std::unique_ptr<QQmlComponent> delegate; 0069 QUrl source; 0070 std::map<Output *, std::unique_ptr<QQmlContext>> contexts; 0071 std::map<Output *, std::unique_ptr<QQmlIncubator>> incubators; 0072 std::map<Output *, std::unique_ptr<QuickSceneView>> views; 0073 QPointer<QuickSceneView> mouseImplicitGrab; 0074 bool running = false; 0075 }; 0076 0077 bool QuickSceneEffectPrivate::isItemOnScreen(QQuickItem *item, Output *screen) const 0078 { 0079 if (!item || !screen || !views.contains(screen)) { 0080 return false; 0081 } 0082 0083 const auto &view = views.at(screen); 0084 return item->window() == view->window(); 0085 } 0086 0087 QuickSceneView::QuickSceneView(QuickSceneEffect *effect, Output *screen) 0088 : OffscreenQuickView(ExportMode::Texture, false) 0089 , m_effect(effect) 0090 , m_screen(screen) 0091 { 0092 setGeometry(screen->geometry()); 0093 connect(screen, &Output::geometryChanged, this, [this, screen]() { 0094 setGeometry(screen->geometry()); 0095 }); 0096 0097 s_views.insert(window(), this); 0098 } 0099 0100 QuickSceneView::~QuickSceneView() 0101 { 0102 s_views.remove(window()); 0103 } 0104 0105 QQuickItem *QuickSceneView::rootItem() const 0106 { 0107 return m_rootItem.get(); 0108 } 0109 0110 void QuickSceneView::setRootItem(QQuickItem *item) 0111 { 0112 Q_ASSERT_X(item, "setRootItem", "root item cannot be null"); 0113 m_rootItem.reset(item); 0114 m_rootItem->setParentItem(contentItem()); 0115 0116 auto updateSize = [this]() { 0117 m_rootItem->setSize(contentItem()->size()); 0118 }; 0119 updateSize(); 0120 connect(contentItem(), &QQuickItem::widthChanged, m_rootItem.get(), updateSize); 0121 connect(contentItem(), &QQuickItem::heightChanged, m_rootItem.get(), updateSize); 0122 } 0123 0124 QuickSceneEffect *QuickSceneView::effect() const 0125 { 0126 return m_effect; 0127 } 0128 0129 Output *QuickSceneView::screen() const 0130 { 0131 return m_screen; 0132 } 0133 0134 bool QuickSceneView::isDirty() const 0135 { 0136 return m_dirty; 0137 } 0138 0139 void QuickSceneView::markDirty() 0140 { 0141 m_dirty = true; 0142 } 0143 0144 void QuickSceneView::resetDirty() 0145 { 0146 m_dirty = false; 0147 } 0148 0149 void QuickSceneView::scheduleRepaint() 0150 { 0151 markDirty(); 0152 effects->addRepaint(geometry()); 0153 } 0154 0155 QuickSceneView *QuickSceneView::findView(QQuickItem *item) 0156 { 0157 return s_views.value(item->window()); 0158 } 0159 0160 QuickSceneView *QuickSceneView::qmlAttachedProperties(QObject *object) 0161 { 0162 QQuickItem *item = qobject_cast<QQuickItem *>(object); 0163 if (item) { 0164 if (QuickSceneView *view = findView(item)) { 0165 return view; 0166 } 0167 } 0168 qCWarning(LIBKWINEFFECTS) << "Could not find SceneView for" << object; 0169 return nullptr; 0170 } 0171 0172 QuickSceneEffect::QuickSceneEffect(QObject *parent) 0173 : Effect(parent) 0174 , d(new QuickSceneEffectPrivate) 0175 { 0176 } 0177 0178 QuickSceneEffect::~QuickSceneEffect() 0179 { 0180 } 0181 0182 bool QuickSceneEffect::supported() 0183 { 0184 return effects->compositingType() == OpenGLCompositing; 0185 } 0186 0187 void QuickSceneEffect::checkItemDraggedOutOfScreen(QQuickItem *item) 0188 { 0189 const QRectF globalGeom = QRectF(item->mapToGlobal(QPointF(0, 0)), QSizeF(item->width(), item->height())); 0190 QList<Output *> screens; 0191 0192 for (const auto &[screen, view] : d->views) { 0193 if (!d->isItemOnScreen(item, screen) && screen->geometry().intersects(globalGeom.toRect())) { 0194 screens << screen; 0195 } 0196 } 0197 0198 Q_EMIT itemDraggedOutOfScreen(item, screens); 0199 } 0200 0201 void QuickSceneEffect::checkItemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item) 0202 { 0203 const auto it = std::find_if(d->views.begin(), d->views.end(), [this, globalPos, item](const auto &view) { 0204 Output *screen = view.first; 0205 return !d->isItemOnScreen(item, screen) && screen->geometry().contains(globalPos.toPoint()); 0206 }); 0207 if (it != d->views.end()) { 0208 Q_EMIT itemDroppedOutOfScreen(globalPos, item, it->first); 0209 } 0210 } 0211 0212 bool QuickSceneEffect::eventFilter(QObject *watched, QEvent *event) 0213 { 0214 if (event->type() == QEvent::CursorChange) { 0215 if (const QWindow *window = qobject_cast<QWindow *>(watched)) { 0216 effects->defineCursor(window->cursor().shape()); 0217 } 0218 } 0219 return false; 0220 } 0221 0222 bool QuickSceneEffect::isRunning() const 0223 { 0224 return d->running; 0225 } 0226 0227 void QuickSceneEffect::setRunning(bool running) 0228 { 0229 if (d->running != running) { 0230 if (running) { 0231 startInternal(); 0232 } else { 0233 stopInternal(); 0234 } 0235 } 0236 } 0237 0238 QUrl QuickSceneEffect::source() const 0239 { 0240 return d->source; 0241 } 0242 0243 void QuickSceneEffect::setSource(const QUrl &url) 0244 { 0245 if (isRunning()) { 0246 qWarning() << "Cannot change QuickSceneEffect.source while running"; 0247 return; 0248 } 0249 if (d->source != url) { 0250 d->source = url; 0251 d->delegate.reset(); 0252 } 0253 } 0254 0255 QQmlComponent *QuickSceneEffect::delegate() const 0256 { 0257 return d->delegate.get(); 0258 } 0259 0260 void QuickSceneEffect::setDelegate(QQmlComponent *delegate) 0261 { 0262 if (isRunning()) { 0263 qWarning() << "Cannot change QuickSceneEffect.source while running"; 0264 return; 0265 } 0266 if (d->delegate.get() != delegate) { 0267 d->source = QUrl(); 0268 d->delegate.reset(delegate); 0269 Q_EMIT delegateChanged(); 0270 } 0271 } 0272 0273 QuickSceneView *QuickSceneEffect::viewForScreen(Output *screen) const 0274 { 0275 const auto it = d->views.find(screen); 0276 return it == d->views.end() ? nullptr : it->second.get(); 0277 } 0278 0279 QuickSceneView *QuickSceneEffect::viewAt(const QPoint &pos) const 0280 { 0281 const auto it = std::find_if(d->views.begin(), d->views.end(), [pos](const auto &view) { 0282 return view.second->geometry().contains(pos); 0283 }); 0284 return it == d->views.end() ? nullptr : it->second.get(); 0285 } 0286 0287 QuickSceneView *QuickSceneEffect::activeView() const 0288 { 0289 auto it = std::find_if(d->views.begin(), d->views.end(), [](const auto &view) { 0290 return view.second->window()->activeFocusItem(); 0291 }); 0292 if (it == d->views.end()) { 0293 it = d->views.find(effects->activeScreen()); 0294 } 0295 return it == d->views.end() ? nullptr : it->second.get(); 0296 } 0297 0298 KWin::QuickSceneView *QuickSceneEffect::getView(Qt::Edge edge) 0299 { 0300 auto screenView = activeView(); 0301 0302 QuickSceneView *candidate = nullptr; 0303 0304 for (const auto &[screen, view] : d->views) { 0305 switch (edge) { 0306 case Qt::LeftEdge: 0307 if (view->geometry().left() < screenView->geometry().left()) { 0308 // Look for the nearest view from the current 0309 if (!candidate || view->geometry().left() > candidate->geometry().left() || (view->geometry().left() == candidate->geometry().left() && view->geometry().top() > candidate->geometry().top())) { 0310 candidate = view.get(); 0311 } 0312 } 0313 break; 0314 case Qt::TopEdge: 0315 if (view->geometry().top() < screenView->geometry().top()) { 0316 if (!candidate || view->geometry().top() > candidate->geometry().top() || (view->geometry().top() == candidate->geometry().top() && view->geometry().left() > candidate->geometry().left())) { 0317 candidate = view.get(); 0318 } 0319 } 0320 break; 0321 case Qt::RightEdge: 0322 if (view->geometry().right() > screenView->geometry().right()) { 0323 if (!candidate || view->geometry().right() < candidate->geometry().right() || (view->geometry().right() == candidate->geometry().right() && view->geometry().top() > candidate->geometry().top())) { 0324 candidate = view.get(); 0325 } 0326 } 0327 break; 0328 case Qt::BottomEdge: 0329 if (view->geometry().bottom() > screenView->geometry().bottom()) { 0330 if (!candidate || view->geometry().bottom() < candidate->geometry().bottom() || (view->geometry().bottom() == candidate->geometry().bottom() && view->geometry().left() > candidate->geometry().left())) { 0331 candidate = view.get(); 0332 } 0333 } 0334 break; 0335 } 0336 } 0337 0338 return candidate; 0339 } 0340 0341 void QuickSceneEffect::activateView(QuickSceneView *view) 0342 { 0343 if (!view) { 0344 return; 0345 } 0346 0347 auto *av = activeView(); 0348 // Already properly active? 0349 if (view == av && av->window()->activeFocusItem()) { 0350 return; 0351 } 0352 0353 for (const auto &[screen, otherView] : d->views) { 0354 if (otherView.get() == view && !view->window()->activeFocusItem()) { 0355 QFocusEvent focusEvent(QEvent::FocusIn, Qt::ActiveWindowFocusReason); 0356 qApp->sendEvent(view->window(), &focusEvent); 0357 } else if (otherView.get() != view && otherView->window()->activeFocusItem()) { 0358 QFocusEvent focusEvent(QEvent::FocusOut, Qt::ActiveWindowFocusReason); 0359 qApp->sendEvent(otherView->window(), &focusEvent); 0360 } 0361 } 0362 0363 Q_EMIT activeViewChanged(view); 0364 } 0365 0366 // Screen views are repainted just before kwin performs its compositing cycle to avoid stalling for vblank 0367 void QuickSceneEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) 0368 { 0369 if (effects->waylandDisplay()) { 0370 const auto it = d->views.find(data.screen); 0371 if (it != d->views.end() && it->second->isDirty()) { 0372 it->second->resetDirty(); 0373 it->second->update(); 0374 } 0375 } else { 0376 for (const auto &[screen, screenView] : d->views) { 0377 if (screenView->isDirty()) { 0378 screenView->resetDirty(); 0379 screenView->update(); 0380 } 0381 } 0382 } 0383 } 0384 0385 void QuickSceneEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *screen) 0386 { 0387 if (effects->waylandDisplay()) { 0388 const auto it = d->views.find(screen); 0389 if (it != d->views.end()) { 0390 effects->renderOffscreenQuickView(renderTarget, viewport, it->second.get()); 0391 } 0392 } else { 0393 for (const auto &[screen, screenView] : d->views) { 0394 effects->renderOffscreenQuickView(renderTarget, viewport, screenView.get()); 0395 } 0396 } 0397 } 0398 0399 bool QuickSceneEffect::isActive() const 0400 { 0401 return !d->views.empty() && !effects->isScreenLocked(); 0402 } 0403 0404 QVariantMap QuickSceneEffect::initialProperties(Output *screen) 0405 { 0406 return QVariantMap(); 0407 } 0408 0409 void QuickSceneEffect::handleScreenAdded(Output *screen) 0410 { 0411 addScreen(screen); 0412 } 0413 0414 void QuickSceneEffect::handleScreenRemoved(Output *screen) 0415 { 0416 d->views.erase(screen); 0417 d->incubators.erase(screen); 0418 d->contexts.erase(screen); 0419 } 0420 0421 void QuickSceneEffect::addScreen(Output *screen) 0422 { 0423 auto properties = initialProperties(screen); 0424 properties["width"] = screen->geometry().width(); 0425 properties["height"] = screen->geometry().height(); 0426 0427 auto incubator = new QuickSceneViewIncubator(this, screen, [this, screen](QuickSceneViewIncubator *incubator) { 0428 if (incubator->isReady()) { 0429 auto view = incubator->result(); 0430 if (view->contentItem()) { 0431 view->contentItem()->setFocus(false); 0432 } 0433 connect(view.get(), &QuickSceneView::renderRequested, view.get(), &QuickSceneView::scheduleRepaint); 0434 connect(view.get(), &QuickSceneView::sceneChanged, view.get(), &QuickSceneView::scheduleRepaint); 0435 view->scheduleRepaint(); 0436 // view is returned via invokables elsewhere 0437 QJSEngine::setObjectOwnership(view.get(), QJSEngine::CppOwnership); 0438 d->views[screen] = std::move(view); 0439 } else if (incubator->isError()) { 0440 qCWarning(LIBKWINEFFECTS) << "Could not create a view for QML file" << d->delegate->url(); 0441 qCWarning(LIBKWINEFFECTS) << incubator->errors(); 0442 } 0443 }); 0444 incubator->setInitialProperties(properties); 0445 0446 QQmlContext *parentContext; 0447 if (QQmlContext *context = d->delegate->creationContext()) { 0448 parentContext = context; 0449 } else if (QQmlContext *context = qmlContext(this)) { 0450 parentContext = context; 0451 } else { 0452 parentContext = d->delegate->engine()->rootContext(); 0453 } 0454 QQmlContext *context = new QQmlContext(parentContext); 0455 0456 d->contexts[screen].reset(context); 0457 d->incubators[screen].reset(incubator); 0458 d->delegate->create(*incubator, context); 0459 } 0460 0461 void QuickSceneEffect::startInternal() 0462 { 0463 if (effects->activeFullScreenEffect()) { 0464 return; 0465 } 0466 0467 if (!d->delegate) { 0468 if (Q_UNLIKELY(d->source.isEmpty())) { 0469 qWarning() << "QuickSceneEffect.source is empty. Did you forget to call setSource()?"; 0470 return; 0471 } 0472 0473 d->delegate = std::make_unique<QQmlComponent>(effects->qmlEngine()); 0474 d->delegate->loadUrl(d->source); 0475 if (d->delegate->isError()) { 0476 qWarning().nospace() << "Failed to load " << d->source << ": " << d->delegate->errors(); 0477 d->delegate.reset(); 0478 return; 0479 } 0480 Q_EMIT delegateChanged(); 0481 } 0482 0483 if (!d->delegate->isReady()) { 0484 return; 0485 } 0486 0487 effects->setActiveFullScreenEffect(this); 0488 d->running = true; 0489 0490 // Install an event filter to monitor cursor shape changes. 0491 qApp->installEventFilter(this); 0492 0493 const QList<Output *> screens = effects->screens(); 0494 for (Output *screen : screens) { 0495 addScreen(screen); 0496 } 0497 0498 // Ensure one view has an active focus item 0499 activateView(activeView()); 0500 0501 connect(effects, &EffectsHandler::screenAdded, this, &QuickSceneEffect::handleScreenAdded); 0502 connect(effects, &EffectsHandler::screenRemoved, this, &QuickSceneEffect::handleScreenRemoved); 0503 0504 effects->grabKeyboard(this); 0505 effects->startMouseInterception(this, Qt::ArrowCursor); 0506 } 0507 0508 void QuickSceneEffect::stopInternal() 0509 { 0510 disconnect(effects, &EffectsHandler::screenAdded, this, &QuickSceneEffect::handleScreenAdded); 0511 disconnect(effects, &EffectsHandler::screenRemoved, this, &QuickSceneEffect::handleScreenRemoved); 0512 0513 d->incubators.clear(); 0514 d->views.clear(); 0515 d->contexts.clear(); 0516 d->running = false; 0517 qApp->removeEventFilter(this); 0518 effects->ungrabKeyboard(); 0519 effects->stopMouseInterception(this); 0520 effects->setActiveFullScreenEffect(nullptr); 0521 effects->addRepaintFull(); 0522 } 0523 0524 void QuickSceneEffect::windowInputMouseEvent(QEvent *event) 0525 { 0526 Qt::MouseButtons buttons; 0527 QPoint globalPosition; 0528 if (QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent *>(event)) { 0529 buttons = mouseEvent->buttons(); 0530 globalPosition = mouseEvent->globalPos(); 0531 } else if (QWheelEvent *wheelEvent = dynamic_cast<QWheelEvent *>(event)) { 0532 buttons = wheelEvent->buttons(); 0533 globalPosition = wheelEvent->globalPosition().toPoint(); 0534 } else { 0535 return; 0536 } 0537 0538 if (buttons) { 0539 if (!d->mouseImplicitGrab) { 0540 d->mouseImplicitGrab = viewAt(globalPosition); 0541 } 0542 } 0543 0544 QuickSceneView *target = d->mouseImplicitGrab; 0545 if (!target) { 0546 target = viewAt(globalPosition); 0547 } 0548 0549 if (!buttons) { 0550 d->mouseImplicitGrab = nullptr; 0551 } 0552 0553 if (target) { 0554 if (buttons) { 0555 activateView(target); 0556 } 0557 target->forwardMouseEvent(event); 0558 } 0559 } 0560 0561 void QuickSceneEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent) 0562 { 0563 auto *screenView = activeView(); 0564 0565 if (screenView) { 0566 // ActiveView may not have an activeFocusItem yet 0567 activateView(screenView); 0568 screenView->forwardKeyEvent(keyEvent); 0569 } 0570 } 0571 0572 bool QuickSceneEffect::touchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time) 0573 { 0574 for (const auto &[screen, screenView] : d->views) { 0575 if (screenView->geometry().contains(pos.toPoint())) { 0576 activateView(screenView.get()); 0577 return screenView->forwardTouchDown(id, pos, time); 0578 } 0579 } 0580 return false; 0581 } 0582 0583 bool QuickSceneEffect::touchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time) 0584 { 0585 for (const auto &[screen, screenView] : d->views) { 0586 if (screenView->geometry().contains(pos.toPoint())) { 0587 return screenView->forwardTouchMotion(id, pos, time); 0588 } 0589 } 0590 return false; 0591 } 0592 0593 bool QuickSceneEffect::touchUp(qint32 id, std::chrono::microseconds time) 0594 { 0595 for (const auto &[screen, screenView] : d->views) { 0596 if (screenView->forwardTouchUp(id, time)) { 0597 return true; 0598 } 0599 } 0600 return false; 0601 } 0602 0603 } // namespace KWin 0604 0605 #include "moc_quickeffect.cpp"