KWin
Loading...
Searching...
No Matches
quickeffect.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
8#include "core/output.h"
10
11#include "logging_p.h"
12
13#include <QQmlContext>
14#include <QQmlEngine>
15#include <QQmlIncubator>
16#include <QQuickItem>
17#include <QQuickWindow>
18
19namespace KWin
20{
21
22static QHash<QQuickWindow *, QuickSceneView *> s_views;
23
24class QuickSceneViewIncubator : public QQmlIncubator
25{
26public:
27 QuickSceneViewIncubator(QuickSceneEffect *effect, Output *screen, const std::function<void(QuickSceneViewIncubator *)> &statusChangedCallback)
28 : QQmlIncubator(QQmlIncubator::Asynchronous)
29 , m_effect(effect)
30 , m_screen(screen)
31 , m_statusChangedCallback(statusChangedCallback)
32 {
33 }
34
35 std::unique_ptr<QuickSceneView> result()
36 {
37 return std::move(m_view);
38 }
39
40 void setInitialState(QObject *object) override
41 {
42 m_view = std::make_unique<QuickSceneView>(m_effect, m_screen);
43 m_view->setAutomaticRepaint(false);
44 m_view->setRootItem(qobject_cast<QQuickItem *>(object));
45 }
46
47 void statusChanged(QQmlIncubator::Status status) override
48 {
49 m_statusChangedCallback(this);
50 }
51
52private:
53 QuickSceneEffect *m_effect;
54 Output *m_screen;
55 std::function<void(QuickSceneViewIncubator *)> m_statusChangedCallback;
56 std::unique_ptr<QuickSceneView> m_view;
57};
58
60{
61public:
63 {
64 return effect->d.get();
65 }
66 bool isItemOnScreen(QQuickItem *item, Output *screen) const;
67
68 std::unique_ptr<QQmlComponent> delegate;
69 QUrl source;
70 std::map<Output *, std::unique_ptr<QQmlContext>> contexts;
71 std::map<Output *, std::unique_ptr<QQmlIncubator>> incubators;
72 std::map<Output *, std::unique_ptr<QuickSceneView>> views;
73 QPointer<QuickSceneView> mouseImplicitGrab;
74 bool running = false;
75};
76
77bool QuickSceneEffectPrivate::isItemOnScreen(QQuickItem *item, Output *screen) const
78{
79 if (!item || !screen || !views.contains(screen)) {
80 return false;
81 }
82
83 const auto &view = views.at(screen);
84 return item->window() == view->window();
85}
86
88 : OffscreenQuickView(ExportMode::Texture, false)
89 , m_effect(effect)
90 , m_screen(screen)
91{
93 connect(screen, &Output::geometryChanged, this, [this, screen]() {
95 });
96
97 s_views.insert(window(), this);
98}
99
101{
102 s_views.remove(window());
103}
104
105QQuickItem *QuickSceneView::rootItem() const
106{
107 return m_rootItem.get();
108}
109
110void QuickSceneView::setRootItem(QQuickItem *item)
111{
112 Q_ASSERT_X(item, "setRootItem", "root item cannot be null");
113 m_rootItem.reset(item);
114 m_rootItem->setParentItem(contentItem());
115
116 auto updateSize = [this]() {
117 m_rootItem->setSize(contentItem()->size());
118 };
119 updateSize();
120 connect(contentItem(), &QQuickItem::widthChanged, m_rootItem.get(), updateSize);
121 connect(contentItem(), &QQuickItem::heightChanged, m_rootItem.get(), updateSize);
122}
123
125{
126 return m_effect;
127}
128
130{
131 return m_screen;
132}
133
135{
136 return m_dirty;
137}
138
140{
141 m_dirty = true;
142}
143
145{
146 m_dirty = false;
147}
148
154
156{
157 return s_views.value(item->window());
158}
159
161{
162 QQuickItem *item = qobject_cast<QQuickItem *>(object);
163 if (item) {
164 if (QuickSceneView *view = findView(item)) {
165 return view;
166 }
167 }
168 qCWarning(LIBKWINEFFECTS) << "Could not find SceneView for" << object;
169 return nullptr;
170}
171
173 : Effect(parent)
175{
176}
177
181
186
188{
189 const QRectF globalGeom = QRectF(item->mapToGlobal(QPointF(0, 0)), QSizeF(item->width(), item->height()));
190 QList<Output *> screens;
191
192 for (const auto &[screen, view] : d->views) {
193 if (!d->isItemOnScreen(item, screen) && screen->geometry().intersects(globalGeom.toRect())) {
194 screens << screen;
195 }
196 }
197
198 Q_EMIT itemDraggedOutOfScreen(item, screens);
199}
200
201void QuickSceneEffect::checkItemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item)
202{
203 const auto it = std::find_if(d->views.begin(), d->views.end(), [this, globalPos, item](const auto &view) {
204 Output *screen = view.first;
205 return !d->isItemOnScreen(item, screen) && screen->geometry().contains(globalPos.toPoint());
206 });
207 if (it != d->views.end()) {
208 Q_EMIT itemDroppedOutOfScreen(globalPos, item, it->first);
209 }
210}
211
212bool QuickSceneEffect::eventFilter(QObject *watched, QEvent *event)
213{
214 if (event->type() == QEvent::CursorChange) {
215 if (const QWindow *window = qobject_cast<QWindow *>(watched)) {
216 effects->defineCursor(window->cursor().shape());
217 }
218 }
219 return false;
220}
221
223{
224 return d->running;
225}
226
228{
229 if (d->running != running) {
230 if (running) {
231 startInternal();
232 } else {
233 stopInternal();
234 }
235 }
236}
237
239{
240 return d->source;
241}
242
243void QuickSceneEffect::setSource(const QUrl &url)
244{
245 if (isRunning()) {
246 qWarning() << "Cannot change QuickSceneEffect.source while running";
247 return;
248 }
249 if (d->source != url) {
250 d->source = url;
251 d->delegate.reset();
252 }
253}
254
255QQmlComponent *QuickSceneEffect::delegate() const
256{
257 return d->delegate.get();
258}
259
260void QuickSceneEffect::setDelegate(QQmlComponent *delegate)
261{
262 if (isRunning()) {
263 qWarning() << "Cannot change QuickSceneEffect.source while running";
264 return;
265 }
266 if (d->delegate.get() != delegate) {
267 d->source = QUrl();
268 d->delegate.reset(delegate);
269 Q_EMIT delegateChanged();
270 }
271}
272
274{
275 const auto it = d->views.find(screen);
276 return it == d->views.end() ? nullptr : it->second.get();
277}
278
280{
281 const auto it = std::find_if(d->views.begin(), d->views.end(), [pos](const auto &view) {
282 return view.second->geometry().contains(pos);
283 });
284 return it == d->views.end() ? nullptr : it->second.get();
285}
286
288{
289 auto it = std::find_if(d->views.begin(), d->views.end(), [](const auto &view) {
290 return view.second->window()->activeFocusItem();
291 });
292 if (it == d->views.end()) {
293 it = d->views.find(effects->activeScreen());
294 }
295 return it == d->views.end() ? nullptr : it->second.get();
296}
297
299{
300 auto screenView = activeView();
301
302 QuickSceneView *candidate = nullptr;
303
304 for (const auto &[screen, view] : d->views) {
305 switch (edge) {
306 case Qt::LeftEdge:
307 if (view->geometry().left() < screenView->geometry().left()) {
308 // Look for the nearest view from the current
309 if (!candidate || view->geometry().left() > candidate->geometry().left() || (view->geometry().left() == candidate->geometry().left() && view->geometry().top() > candidate->geometry().top())) {
310 candidate = view.get();
311 }
312 }
313 break;
314 case Qt::TopEdge:
315 if (view->geometry().top() < screenView->geometry().top()) {
316 if (!candidate || view->geometry().top() > candidate->geometry().top() || (view->geometry().top() == candidate->geometry().top() && view->geometry().left() > candidate->geometry().left())) {
317 candidate = view.get();
318 }
319 }
320 break;
321 case Qt::RightEdge:
322 if (view->geometry().right() > screenView->geometry().right()) {
323 if (!candidate || view->geometry().right() < candidate->geometry().right() || (view->geometry().right() == candidate->geometry().right() && view->geometry().top() > candidate->geometry().top())) {
324 candidate = view.get();
325 }
326 }
327 break;
328 case Qt::BottomEdge:
329 if (view->geometry().bottom() > screenView->geometry().bottom()) {
330 if (!candidate || view->geometry().bottom() < candidate->geometry().bottom() || (view->geometry().bottom() == candidate->geometry().bottom() && view->geometry().left() > candidate->geometry().left())) {
331 candidate = view.get();
332 }
333 }
334 break;
335 }
336 }
337
338 return candidate;
339}
340
342{
343 if (!view) {
344 return;
345 }
346
347 auto *av = activeView();
348 // Already properly active?
349 if (view == av && av->window()->activeFocusItem()) {
350 return;
351 }
352
353 for (const auto &[screen, otherView] : d->views) {
354 if (otherView.get() == view && !view->window()->activeFocusItem()) {
355 QFocusEvent focusEvent(QEvent::FocusIn, Qt::ActiveWindowFocusReason);
356 qApp->sendEvent(view->window(), &focusEvent);
357 } else if (otherView.get() != view && otherView->window()->activeFocusItem()) {
358 QFocusEvent focusEvent(QEvent::FocusOut, Qt::ActiveWindowFocusReason);
359 qApp->sendEvent(otherView->window(), &focusEvent);
360 }
361 }
362
363 Q_EMIT activeViewChanged(view);
364}
365
366// Screen views are repainted just before kwin performs its compositing cycle to avoid stalling for vblank
367void QuickSceneEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
368{
369 if (effects->waylandDisplay()) {
370 const auto it = d->views.find(data.screen);
371 if (it != d->views.end() && it->second->isDirty()) {
372 it->second->resetDirty();
373 it->second->update();
374 }
375 } else {
376 for (const auto &[screen, screenView] : d->views) {
377 if (screenView->isDirty()) {
378 screenView->resetDirty();
379 screenView->update();
380 }
381 }
382 }
383}
384
385void QuickSceneEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
386{
387 if (effects->waylandDisplay()) {
388 const auto it = d->views.find(screen);
389 if (it != d->views.end()) {
390 effects->renderOffscreenQuickView(renderTarget, viewport, it->second.get());
391 }
392 } else {
393 for (const auto &[screen, screenView] : d->views) {
394 effects->renderOffscreenQuickView(renderTarget, viewport, screenView.get());
395 }
396 }
397}
398
400{
401 return !d->views.empty() && !effects->isScreenLocked();
402}
403
405{
406 return QVariantMap();
407}
408
409void QuickSceneEffect::handleScreenAdded(Output *screen)
410{
411 addScreen(screen);
412}
413
414void QuickSceneEffect::handleScreenRemoved(Output *screen)
415{
416 d->views.erase(screen);
417 d->incubators.erase(screen);
418 d->contexts.erase(screen);
419}
420
421void QuickSceneEffect::addScreen(Output *screen)
422{
423 auto properties = initialProperties(screen);
424 properties["width"] = screen->geometry().width();
425 properties["height"] = screen->geometry().height();
426
427 auto incubator = new QuickSceneViewIncubator(this, screen, [this, screen](QuickSceneViewIncubator *incubator) {
428 if (incubator->isReady()) {
429 auto view = incubator->result();
430 if (view->contentItem()) {
431 view->contentItem()->setFocus(false);
432 }
433 connect(view.get(), &QuickSceneView::renderRequested, view.get(), &QuickSceneView::scheduleRepaint);
434 connect(view.get(), &QuickSceneView::sceneChanged, view.get(), &QuickSceneView::scheduleRepaint);
435 view->scheduleRepaint();
436 // view is returned via invokables elsewhere
437 QJSEngine::setObjectOwnership(view.get(), QJSEngine::CppOwnership);
438 d->views[screen] = std::move(view);
439 } else if (incubator->isError()) {
440 qCWarning(LIBKWINEFFECTS) << "Could not create a view for QML file" << d->delegate->url();
441 qCWarning(LIBKWINEFFECTS) << incubator->errors();
442 }
443 });
444 incubator->setInitialProperties(properties);
445
446 QQmlContext *parentContext;
447 if (QQmlContext *context = d->delegate->creationContext()) {
448 parentContext = context;
449 } else if (QQmlContext *context = qmlContext(this)) {
450 parentContext = context;
451 } else {
452 parentContext = d->delegate->engine()->rootContext();
453 }
454 QQmlContext *context = new QQmlContext(parentContext);
455
456 d->contexts[screen].reset(context);
457 d->incubators[screen].reset(incubator);
458 d->delegate->create(*incubator, context);
459}
460
461void QuickSceneEffect::startInternal()
462{
464 return;
465 }
466
467 if (!d->delegate) {
468 if (Q_UNLIKELY(d->source.isEmpty())) {
469 qWarning() << "QuickSceneEffect.source is empty. Did you forget to call setSource()?";
470 return;
471 }
472
473 d->delegate = std::make_unique<QQmlComponent>(effects->qmlEngine());
474 d->delegate->loadUrl(d->source);
475 if (d->delegate->isError()) {
476 qWarning().nospace() << "Failed to load " << d->source << ": " << d->delegate->errors();
477 d->delegate.reset();
478 return;
479 }
480 Q_EMIT delegateChanged();
481 }
482
483 if (!d->delegate->isReady()) {
484 return;
485 }
486
488 d->running = true;
489
490 // Install an event filter to monitor cursor shape changes.
491 qApp->installEventFilter(this);
492
493 const QList<Output *> screens = effects->screens();
494 for (Output *screen : screens) {
495 addScreen(screen);
496 }
497
498 // Ensure one view has an active focus item
499 activateView(activeView());
500
501 connect(effects, &EffectsHandler::screenAdded, this, &QuickSceneEffect::handleScreenAdded);
502 connect(effects, &EffectsHandler::screenRemoved, this, &QuickSceneEffect::handleScreenRemoved);
503
504 effects->grabKeyboard(this);
505 effects->startMouseInterception(this, Qt::ArrowCursor);
506}
507
508void QuickSceneEffect::stopInternal()
509{
510 disconnect(effects, &EffectsHandler::screenAdded, this, &QuickSceneEffect::handleScreenAdded);
511 disconnect(effects, &EffectsHandler::screenRemoved, this, &QuickSceneEffect::handleScreenRemoved);
512
513 d->incubators.clear();
514 d->views.clear();
515 d->contexts.clear();
516 d->running = false;
517 qApp->removeEventFilter(this);
522}
523
524void QuickSceneEffect::windowInputMouseEvent(QEvent *event)
525{
526 Qt::MouseButtons buttons;
527 QPoint globalPosition;
528 if (QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent *>(event)) {
529 buttons = mouseEvent->buttons();
530 globalPosition = mouseEvent->globalPos();
531 } else if (QWheelEvent *wheelEvent = dynamic_cast<QWheelEvent *>(event)) {
532 buttons = wheelEvent->buttons();
533 globalPosition = wheelEvent->globalPosition().toPoint();
534 } else {
535 return;
536 }
537
538 if (buttons) {
539 if (!d->mouseImplicitGrab) {
540 d->mouseImplicitGrab = viewAt(globalPosition);
541 }
542 }
543
544 QuickSceneView *target = d->mouseImplicitGrab;
545 if (!target) {
546 target = viewAt(globalPosition);
547 }
548
549 if (!buttons) {
550 d->mouseImplicitGrab = nullptr;
551 }
552
553 if (target) {
554 if (buttons) {
555 activateView(target);
556 }
557 target->forwardMouseEvent(event);
558 }
559}
560
561void QuickSceneEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent)
562{
563 auto *screenView = activeView();
564
565 if (screenView) {
566 // ActiveView may not have an activeFocusItem yet
567 activateView(screenView);
568 screenView->forwardKeyEvent(keyEvent);
569 }
570}
571
572bool QuickSceneEffect::touchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time)
573{
574 for (const auto &[screen, screenView] : d->views) {
575 if (screenView->geometry().contains(pos.toPoint())) {
576 activateView(screenView.get());
577 return screenView->forwardTouchDown(id, pos, time);
578 }
579 }
580 return false;
581}
582
583bool QuickSceneEffect::touchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time)
584{
585 for (const auto &[screen, screenView] : d->views) {
586 if (screenView->geometry().contains(pos.toPoint())) {
587 return screenView->forwardTouchMotion(id, pos, time);
588 }
589 }
590 return false;
591}
592
593bool QuickSceneEffect::touchUp(qint32 id, std::chrono::microseconds time)
594{
595 for (const auto &[screen, screenView] : d->views) {
596 if (screenView->forwardTouchUp(id, time)) {
597 return true;
598 }
599 }
600 return false;
601}
602
603} // namespace KWin
604
605#include "moc_quickeffect.cpp"
Base class for all KWin effects.
Definition effect.h:535
Display * waylandDisplay() const
void stopMouseInterception(Effect *effect)
QQmlEngine * qmlEngine() const
Q_SCRIPTABLE void addRepaint(const QRectF &r)
CompositingType compositingType
virtual void defineCursor(Qt::CursorShape shape)
void setActiveFullScreenEffect(Effect *e)
void startMouseInterception(Effect *effect, Qt::CursorShape shape)
bool grabKeyboard(Effect *effect)
QList< Output * > screens() const
void renderOffscreenQuickView(const RenderTarget &renderTarget, const RenderViewport &viewport, OffscreenQuickView *effectQuickView) const
KWin::Output * activeScreen
Q_SCRIPTABLE void addRepaintFull()
Effect * activeFullScreenEffect() const
The KwinQuickView class provides a convenient API for exporting QtQuick scenes as buffers that can be...
QQuickWindow * window() const
QQuickItem * contentItem() const
void setGeometry(const QRect &rect)
void forwardMouseEvent(QEvent *mouseEvent)
void geometryChanged()
QRect geometry
Definition output.h:134
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen) override
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override
QuickSceneEffect(QObject *parent=nullptr)
QQmlComponent * delegate
Definition quickeffect.h:79
QuickSceneView * activeView
Definition quickeffect.h:78
bool eventFilter(QObject *watched, QEvent *event) override
Q_INVOKABLE KWin::QuickSceneView * getView(Qt::Edge edge)
Q_INVOKABLE void activateView(QuickSceneView *view)
void setDelegate(QQmlComponent *delegate)
Q_INVOKABLE QuickSceneView * viewAt(const QPoint &pos) const
bool isActive() const override
Q_INVOKABLE void checkItemDraggedOutOfScreen(QQuickItem *item)
void itemDraggedOutOfScreen(QQuickItem *item, QList< Output * > screens)
void itemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item, Output *screen)
void activeViewChanged(KWin::QuickSceneView *view)
virtual QVariantMap initialProperties(Output *screen)
Q_INVOKABLE QuickSceneView * viewForScreen(Output *screen) const
Q_INVOKABLE void checkItemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item)
void setSource(const QUrl &url)
void setRunning(bool running)
QPointer< QuickSceneView > mouseImplicitGrab
std::unique_ptr< QQmlComponent > delegate
std::map< Output *, std::unique_ptr< QuickSceneView > > views
std::map< Output *, std::unique_ptr< QQmlContext > > contexts
std::map< Output *, std::unique_ptr< QQmlIncubator > > incubators
bool isItemOnScreen(QQuickItem *item, Output *screen) const
static QuickSceneEffectPrivate * get(QuickSceneEffect *effect)
void setRootItem(QQuickItem *item)
QQuickItem * rootItem
Definition quickeffect.h:34
QuickSceneEffect * effect
Definition quickeffect.h:32
static QuickSceneView * findView(QQuickItem *item)
static QuickSceneView * qmlAttachedProperties(QObject *object)
QuickSceneView(QuickSceneEffect *effect, Output *screen)
QuickSceneViewIncubator(QuickSceneEffect *effect, Output *screen, const std::function< void(QuickSceneViewIncubator *)> &statusChangedCallback)
std::unique_ptr< QuickSceneView > result()
void statusChanged(QQmlIncubator::Status status) override
void setInitialState(QObject *object) override
@ OpenGLCompositing
Definition globals.h:37
EffectsHandler * effects