1 /*
2   quickoverlay.cpp
3 
4   This file is part of GammaRay, the Qt application inspection and
5   manipulation tool.
6 
7   Copyright (C) 2010-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8   Author: Filipe Azevedo <filipe.azevedo@kdab.com>
9 
10   Licensees holding valid commercial KDAB GammaRay licenses may use this file in
11   accordance with GammaRay Commercial License Agreement provided with the Software.
12 
13   Contact info@kdab.com if any conditions of this licensing are not clear to you.
14 
15   This program is free software; you can redistribute it and/or modify
16   it under the terms of the GNU General Public License as published by
17   the Free Software Foundation, either version 2 of the License, or
18   (at your option) any later version.
19 
20   This program is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23   GNU General Public License for more details.
24 
25   You should have received a copy of the GNU General Public License
26   along with this program.  If not, see <http://www.gnu.org/licenses/>.
27 */
28 
29 #include "quickscreengrabber.h"
30 
31 #include <core/objectdataprovider.h>
32 
33 #include <QDebug>
34 #include <QEvent>
35 #include <QPainter>
36 #include <QQuickWindow>
37 
38 #ifndef QT_NO_OPENGL
39 #include <QOpenGLContext>
40 #include <QOpenGLFunctions>
41 #include <QOpenGLPaintDevice>
42 #endif
43 
44 #include <private/qquickanchors_p.h>
45 #include <private/qquickitem_p.h>
46 #include <private/qquickwindow_p.h>
47 
48 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
49 #include <private/qsgsoftwarerenderer_p.h>
50 #endif
51 
52 #include <algorithm>
53 #include <functional>
54 #include <cmath>
55 
56 namespace GammaRay {
57 
58 class QQuickItemPropertyCache {
59 public:
getPropertyCache(QQuickItem * item)60     static const QQuickItemPropertyCache &getPropertyCache(QQuickItem *item)
61     {
62         static QHash<const QMetaObject*, QQuickItemPropertyCache> s_cache;
63         const QMetaObject* meta = item->metaObject();
64         const auto it = s_cache.constFind(meta);
65         if (it != s_cache.cend())
66             return *it;
67         else
68             return *s_cache.insert(meta, QQuickItemPropertyCache(meta));
69     }
70 
QQuickItemPropertyCache(const QMetaObject * meta)71     explicit QQuickItemPropertyCache(const QMetaObject *meta)
72         : background(property(meta, "background"))
73         , contentItem(property(meta, "contentItem"))
74         , padding(property(meta, "padding"))
75     {
76         if (padding.isValid()) {
77             leftPadding = property(meta, "leftPadding");
78             rightPadding = property(meta, "rightPadding");
79             topPadding = property(meta, "topPadding");
80             bottomPadding = property(meta, "bottomPadding");
81         }
82     }
83 
84     QMetaProperty background;
85     QMetaProperty contentItem;
86     QMetaProperty padding;
87     QMetaProperty leftPadding;
88     QMetaProperty rightPadding;
89     QMetaProperty topPadding;
90     QMetaProperty bottomPadding;
91 
92 private:
property(const QMetaObject * meta,const char * name)93     static inline QMetaProperty property(const QMetaObject *meta, const char *name) {
94          return meta->property(meta->indexOfProperty(name));
95     }
96 };
97 
98 // We need random colors, but we also want the item
99 // to keep its random color during scene changes to avoid
100 // flickering due to color change.
101 static QHash<QQuickItem *, QColor> s_itemsColor;
102 
colorForItem(QQuickItem * item)103 static QColor colorForItem(QQuickItem *item)
104 {
105     QColor color = s_itemsColor.value(item, QColor());
106 
107     if (!color.isValid()) {
108         const auto h = qHash(ObjectDataProvider::shortTypeName(item));
109         color = QColor::fromHsv(h % 360, 64 + h % 192, 128 + h % 128, 64);
110         s_itemsColor[item] = color;
111     }
112 
113     return color;
114 }
115 
quickItemZLessThan(QQuickItem * lhs,QQuickItem * rhs)116 static bool quickItemZLessThan(QQuickItem *lhs, QQuickItem *rhs)
117 {
118     return lhs->z() < rhs->z();
119 }
120 
quickItemZGreaterThan(QQuickItem * lhs,QQuickItem * rhs)121 static bool quickItemZGreaterThan(QQuickItem *lhs, QQuickItem *rhs)
122 {
123     return lhs->z() > rhs->z();
124 }
125 
findItemByClassName(const char * className,QQuickItem * parent,const std::function<void (QQuickItem *)> & walker)126 static QVector<QQuickItem *> findItemByClassName(const char *className, QQuickItem *parent,
127                                                  const std::function<void(QQuickItem *)> &walker)
128 {
129     Q_ASSERT(parent);
130     QVector<QQuickItem *> items;
131 
132     if (!parent->window()) {
133         return items;
134     }
135 
136     if (parent != parent->window()->contentItem() && parent->inherits(className)) {
137         items << parent;
138         walker(parent);
139     }
140 
141     QList<QQuickItem *> childItems = parent->childItems();
142     // direct children of contentItem need to be sorted the over way so overlay is draw on top of the rest
143     if (parent == parent->window()->contentItem()) {
144         std::sort(childItems.begin(), childItems.end(), quickItemZGreaterThan);
145     } else {
146         std::sort(childItems.begin(), childItems.end(), quickItemZLessThan);
147     }
148 
149 
150     for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order
151         const auto childItemChildItems = findItemByClassName(className, childItems.at(i), walker);
152 
153         if (!childItemChildItems.isEmpty())
154             items << childItemChildItems;
155     }
156 
157     return items;
158 }
159 
toplevelItem(QQuickItem * item)160 static QQuickItem *toplevelItem(QQuickItem *item)
161 {
162     Q_ASSERT(item);
163     return item->window()->contentItem();
164 }
165 
itemPos(QQuickItem * item)166 static QPointF itemPos(QQuickItem *item)
167 {
168     Q_ASSERT(item);
169     return {item->x(), item->y()};
170 }
171 
itemSize(QQuickItem * item)172 static QSizeF itemSize(QQuickItem *item)
173 {
174     Q_ASSERT(item);
175     QSizeF size = QSizeF(item->width(), item->height());
176 
177     // Fallback to children rect if needed
178     if (size.isNull()) {
179         size = item->childrenRect().size();
180     }
181 
182     return size;
183 }
184 
itemGeometry(QQuickItem * item)185 static QRectF itemGeometry(QQuickItem *item)
186 {
187     return QRectF(itemPos(item), itemSize(item));
188 }
189 
itemIsLayout(QQuickItem * item)190 static bool itemIsLayout(QQuickItem *item)
191 {
192     Q_ASSERT(item);
193     return item->inherits("QQuickLayout");
194 }
195 }
196 
197 using namespace GammaRay;
198 
ItemOrLayoutFacade(QQuickItem * item)199 ItemOrLayoutFacade::ItemOrLayoutFacade(QQuickItem *item)
200     : m_object(item)
201 {
202 }
203 
layout() const204 QQuickItem *ItemOrLayoutFacade::layout() const
205 {
206     return isLayout() ? asLayout() : asItem();
207 }
208 
item() const209 QQuickItem *ItemOrLayoutFacade::item() const
210 {
211     return isLayout() ? asLayout()->parentItem() : asItem();
212 }
213 
geometry() const214 QRectF ItemOrLayoutFacade::geometry() const
215 {
216     return itemGeometry(isLayout() ? asLayout() : asItem());
217 }
218 
isVisible() const219 bool ItemOrLayoutFacade::isVisible() const
220 {
221     return item() ? item()->isVisible() : false;
222 }
223 
pos() const224 QPointF ItemOrLayoutFacade::pos() const
225 {
226     return isLayout() ? itemGeometry(asLayout()).topLeft() : QPoint(0, 0);
227 }
228 
isLayout() const229 bool ItemOrLayoutFacade::isLayout() const
230 {
231     return itemIsLayout(m_object);
232 }
233 
get(QQuickWindow * window)234 std::unique_ptr<AbstractScreenGrabber> AbstractScreenGrabber::get(QQuickWindow* window)
235 {
236     switch (graphicsApiFor(window)) {
237 #ifndef QT_NO_OPENGL
238         case RenderInfo::OpenGL:
239             return std::unique_ptr<AbstractScreenGrabber>(new OpenGLScreenGrabber(window));
240 #endif
241 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
242         case RenderInfo::Software:
243             return std::unique_ptr<AbstractScreenGrabber>(new SoftwareScreenGrabber(window));
244 #endif
245         default:
246             return nullptr;
247     }
248 }
249 
graphicsApiFor(QQuickWindow * window)250 AbstractScreenGrabber::RenderInfo::GraphicsApi AbstractScreenGrabber::graphicsApiFor(QQuickWindow* window)
251 {
252     if (!window) {
253         return RenderInfo::Unknown;
254     }
255 
256 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
257     return static_cast<RenderInfo::GraphicsApi>(window->rendererInterface()->graphicsApi());
258 #else
259     return RenderInfo::OpenGL;
260 #endif
261 }
262 
AbstractScreenGrabber(QQuickWindow * window)263 AbstractScreenGrabber::AbstractScreenGrabber(QQuickWindow *window)
264     : m_window(window)
265     , m_currentToplevelItem(nullptr)
266 {
267     qRegisterMetaType<GrabbedFrame>();
268 
269     placeOn(ItemOrLayoutFacade());
270 }
271 
272 GammaRay::AbstractScreenGrabber::~AbstractScreenGrabber() = default;
273 
window() const274 QQuickWindow *AbstractScreenGrabber::window() const
275 {
276     return m_window;
277 }
278 
settings() const279 QuickDecorationsSettings AbstractScreenGrabber::settings() const
280 {
281     return m_settings;
282 }
283 
setSettings(const QuickDecorationsSettings & settings)284 void AbstractScreenGrabber::setSettings(const QuickDecorationsSettings &settings)
285 {
286     if (m_settings == settings)
287         return;
288     m_settings = settings;
289     updateOverlay();
290 }
291 
decorationsEnabled() const292 bool AbstractScreenGrabber::decorationsEnabled() const
293 {
294     return m_decorationsEnabled;
295 }
296 
setDecorationsEnabled(bool enabled)297 void AbstractScreenGrabber::setDecorationsEnabled(bool enabled)
298 {
299     if (m_decorationsEnabled == enabled)
300         return;
301 
302     m_decorationsEnabled = enabled;
303     updateOverlay();
304 }
305 
placeOn(const ItemOrLayoutFacade & item)306 void AbstractScreenGrabber::placeOn(const ItemOrLayoutFacade &item)
307 {
308     if (item.isNull()) {
309         if (!m_currentItem.isNull())
310             disconnectItemChanges(m_currentItem.data());
311 
312         if (m_currentToplevelItem) {
313             disconnectTopItemChanges(m_currentToplevelItem);
314             // Ensure the overlay is cleared
315             if (m_currentToplevelItem->window())
316                 m_currentToplevelItem->window()->update();
317         }
318 
319         m_currentToplevelItem = nullptr;
320         m_currentItem.clear();
321 
322         updateOverlay();
323         return;
324     }
325 
326     Q_ASSERT(item.item()->window() == m_window);
327 
328     if (!m_currentItem.isNull())
329         disconnectItemChanges(m_currentItem.data());
330 
331     m_currentItem = item;
332 
333     QQuickItem *toplevel = toplevelItem(item.item());
334     Q_ASSERT(toplevel);
335 
336     if (toplevel != m_currentToplevelItem) {
337         if (m_currentToplevelItem) {
338             disconnectTopItemChanges(m_currentToplevelItem);
339             // Ensure the overlay is cleared
340             m_currentToplevelItem->window()->update();
341         }
342 
343         m_currentToplevelItem = toplevel;
344 
345         connectTopItemChanges(m_currentToplevelItem);
346     }
347 
348     connectItemChanges(m_currentItem.data());
349 
350     updateOverlay();
351 }
352 
initFromItem(QQuickItem * item)353 QuickItemGeometry AbstractScreenGrabber::initFromItem(QQuickItem *item)
354 {
355     QuickItemGeometry itemGeometry;
356 
357     if (!item) {
358         Q_ASSERT(false);
359         return itemGeometry;
360     }
361 
362     QQuickItem *parent = item->parentItem();
363 
364     if (parent) {
365         itemGeometry.itemRect = item->parentItem()->mapRectToScene(
366                     QRectF(item->x(), item->y(), item->width(), item->height()));
367     } else {
368         itemGeometry.itemRect = QRectF(0, 0, item->width(), item->height());
369     }
370 
371     itemGeometry.boundingRect = item->mapRectToScene(item->boundingRect());
372     itemGeometry.childrenRect = item->mapRectToScene(item->childrenRect());
373 
374     const QQuickItemPropertyCache &cache = QQuickItemPropertyCache::getPropertyCache(item);
375 
376     QQuickItem *background = cache.background.read(item).value<QQuickItem *>();
377     if (background)
378         itemGeometry.backgroundRect = background->mapRectToScene(background->boundingRect());
379     QQuickItem *contentItem = cache.contentItem.read(item).value<QQuickItem *>();
380     if (contentItem)
381         itemGeometry.contentItemRect = contentItem->mapRectToScene(contentItem->boundingRect());
382     itemGeometry.transformOriginPoint = item->mapToScene(item->transformOriginPoint());
383 
384     QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item);
385     QQuickAnchors *anchors = itemPriv->_anchors;
386 
387     if (anchors) {
388         QQuickAnchors::Anchors usedAnchors = anchors->usedAnchors();
389         itemGeometry.left = (bool)(usedAnchors &QQuickAnchors::LeftAnchor) || anchors->fill();
390         itemGeometry.right = (bool)(usedAnchors &QQuickAnchors::RightAnchor) || anchors->fill();
391         itemGeometry.top = (bool)(usedAnchors &QQuickAnchors::TopAnchor) || anchors->fill();
392         itemGeometry.bottom = (bool)(usedAnchors &QQuickAnchors::BottomAnchor) || anchors->fill();
393         itemGeometry.baseline = (bool)(usedAnchors & QQuickAnchors::BaselineAnchor);
394         itemGeometry.horizontalCenter = (bool)(usedAnchors &QQuickAnchors::HCenterAnchor)
395                 || anchors->centerIn();
396         itemGeometry.verticalCenter = (bool)(usedAnchors &QQuickAnchors::VCenterAnchor)
397                 || anchors->centerIn();
398         itemGeometry.leftMargin = anchors->leftMargin();
399         itemGeometry.rightMargin = anchors->rightMargin();
400         itemGeometry.topMargin = anchors->topMargin();
401         itemGeometry.bottomMargin = anchors->bottomMargin();
402         itemGeometry.horizontalCenterOffset = anchors->horizontalCenterOffset();
403         itemGeometry.verticalCenterOffset = anchors->verticalCenterOffset();
404         itemGeometry.baselineOffset = anchors->baselineOffset();
405         itemGeometry.margins = anchors->margins();
406     }
407     itemGeometry.x = item->x();
408     itemGeometry.y = item->y();
409 
410     if (cache.padding.isValid()) {
411         itemGeometry.padding = cache.padding.read(item).toReal();
412         itemGeometry.leftPadding = cache.leftPadding.read(item).toReal();
413         itemGeometry.rightPadding = cache.rightPadding.read(item).toReal();
414         itemGeometry.topPadding = cache.topPadding.read(item).toReal();
415         itemGeometry.bottomPadding = cache.bottomPadding.read(item).toReal();
416     } else {
417         itemGeometry.padding = qQNaN();
418         itemGeometry.leftPadding = qQNaN();
419         itemGeometry.rightPadding = qQNaN();
420         itemGeometry.topPadding = qQNaN();
421         itemGeometry.bottomPadding = qQNaN();
422     }
423 
424     itemGeometry.transform = itemPriv->itemToWindowTransform();
425     if (parent) {
426         QQuickItemPrivate *parentPriv = QQuickItemPrivate::get(parent);
427         itemGeometry.parentTransform = parentPriv->itemToWindowTransform();
428     }
429 
430     itemGeometry.traceColor = colorForItem(item);
431     itemGeometry.traceTypeName = ObjectDataProvider::shortTypeName(item);
432     itemGeometry.traceName = ObjectDataProvider::name(item);
433 
434     return itemGeometry;
435 }
436 
gatherRenderInfo()437 void AbstractScreenGrabber::gatherRenderInfo()
438 {
439     // We are in the rendering thread at this point
440     // And the gui thread is locked
441     m_renderInfo.dpr = 1.0;
442     // See QTBUG-53795
443     m_renderInfo.dpr = m_window->effectiveDevicePixelRatio();
444     m_renderInfo.windowSize = m_window->size();
445 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
446     m_renderInfo.graphicsApi = static_cast<RenderInfo::GraphicsApi>(m_window->rendererInterface()->graphicsApi());
447 #else
448     m_renderInfo.graphicsApi = RenderInfo::OpenGL;
449 #endif
450 
451     m_grabbedFrame.itemsGeometry.clear();
452     m_grabbedFrame.itemsGeometryRect = QRectF();
453 
454     if (m_window) {
455         m_grabbedFrame.itemsGeometryRect = QRect(QPoint(), m_renderInfo.windowSize);
456 
457         if (m_settings.componentsTraces) {
458             findItemByClassName("QQuickControl",
459                                 m_window->contentItem(),
460                                 [this](QQuickItem *item) {
461                 if (!item->isVisible())
462                     return;
463                 QuickItemGeometry itemGeometry = initFromItem(item);
464                 m_grabbedFrame.itemsGeometry << itemGeometry;
465                 m_grabbedFrame.itemsGeometryRect |= itemGeometry.itemRect | itemGeometry.childrenRect |
466                         itemGeometry.boundingRect;
467             });
468         } else {
469             QuickItemGeometry itemGeometry;
470             if (!m_currentItem.isNull())
471                 itemGeometry = initFromItem(m_currentItem.data());
472             m_grabbedFrame.itemsGeometry << itemGeometry;
473             m_grabbedFrame.itemsGeometryRect |= itemGeometry.itemRect | itemGeometry.childrenRect |
474                     itemGeometry.boundingRect;
475         }
476     }
477 }
478 
doDrawDecorations(QPainter & painter)479 void AbstractScreenGrabber::doDrawDecorations(QPainter &painter)
480 {
481     if (!m_decorationsEnabled)
482         return;
483 
484     if (m_settings.componentsTraces) {
485         const QuickDecorationsTracesInfo tracesInfo(m_settings,
486                                                     m_grabbedFrame.itemsGeometry,
487                                                     QRectF(QPointF(), m_renderInfo.windowSize),
488                                                     1.0);
489         QuickDecorationsDrawer drawer(QuickDecorationsDrawer::Traces, painter, tracesInfo);
490         drawer.render();
491     } else {
492         const QuickDecorationsRenderInfo renderInfo(m_settings,
493                                                     m_grabbedFrame.itemsGeometry.value(0),
494                                                     QRectF(QPointF(), m_renderInfo.windowSize),
495                                                     1.0);
496         QuickDecorationsDrawer drawer(QuickDecorationsDrawer::Decorations, painter, renderInfo);
497         drawer.render();
498     }
499 }
500 
updateOverlay()501 void AbstractScreenGrabber::updateOverlay()
502 {
503     if (m_window) {
504         if (!m_currentItem.isNull())
505             Q_ASSERT(m_currentItem.item()->window() == m_window);
506 
507         m_window->update();
508     }
509 }
510 
itemParentChanged(QQuickItem * parent)511 void AbstractScreenGrabber::itemParentChanged(QQuickItem *parent)
512 {
513     Q_UNUSED(parent);
514     if (!m_currentItem.isNull())
515         placeOn(m_currentItem);
516 }
517 
itemWindowChanged(QQuickWindow * window)518 void AbstractScreenGrabber::itemWindowChanged(QQuickWindow *window)
519 {
520     if (m_window == window) {
521         if (!m_currentItem.isNull())
522             placeOn(m_currentItem);
523     } else {
524         placeOn(ItemOrLayoutFacade());
525     }
526 }
527 
connectItemChanges(QQuickItem * item)528 void AbstractScreenGrabber::connectItemChanges(QQuickItem *item)
529 {
530     connect(item, &QQuickItem::childrenRectChanged, this, &AbstractScreenGrabber::updateOverlay);
531     connect(item, &QQuickItem::rotationChanged, this, &AbstractScreenGrabber::updateOverlay);
532     connect(item, &QQuickItem::scaleChanged, this, &AbstractScreenGrabber::updateOverlay);
533     connect(item, &QQuickItem::widthChanged, this, &AbstractScreenGrabber::updateOverlay);
534     connect(item, &QQuickItem::heightChanged, this, &AbstractScreenGrabber::updateOverlay);
535     connect(item, &QQuickItem::xChanged, this, &AbstractScreenGrabber::updateOverlay);
536     connect(item, &QQuickItem::yChanged, this, &AbstractScreenGrabber::updateOverlay);
537     connect(item, &QQuickItem::zChanged, this, &AbstractScreenGrabber::updateOverlay);
538     connect(item, &QQuickItem::visibleChanged, this, &AbstractScreenGrabber::updateOverlay);
539     connect(item, &QQuickItem::parentChanged, this, &AbstractScreenGrabber::itemParentChanged);
540     connect(item, &QQuickItem::windowChanged, this, &AbstractScreenGrabber::itemWindowChanged);
541 }
542 
disconnectItemChanges(QQuickItem * item)543 void AbstractScreenGrabber::disconnectItemChanges(QQuickItem *item)
544 {
545     disconnect(item, &QQuickItem::childrenRectChanged, this, &AbstractScreenGrabber::updateOverlay);
546     disconnect(item, &QQuickItem::rotationChanged, this, &AbstractScreenGrabber::updateOverlay);
547     disconnect(item, &QQuickItem::scaleChanged, this, &AbstractScreenGrabber::updateOverlay);
548     disconnect(item, &QQuickItem::widthChanged, this, &AbstractScreenGrabber::updateOverlay);
549     disconnect(item, &QQuickItem::heightChanged, this, &AbstractScreenGrabber::updateOverlay);
550     disconnect(item, &QQuickItem::xChanged, this, &AbstractScreenGrabber::updateOverlay);
551     disconnect(item, &QQuickItem::yChanged, this, &AbstractScreenGrabber::updateOverlay);
552     disconnect(item, &QQuickItem::zChanged, this, &AbstractScreenGrabber::updateOverlay);
553     disconnect(item, &QQuickItem::visibleChanged, this, &AbstractScreenGrabber::updateOverlay);
554     disconnect(item, &QQuickItem::parentChanged, this, &AbstractScreenGrabber::itemParentChanged);
555     disconnect(item, &QQuickItem::windowChanged, this, &AbstractScreenGrabber::itemWindowChanged);
556 }
557 
connectTopItemChanges(QQuickItem * item)558 void AbstractScreenGrabber::connectTopItemChanges(QQuickItem *item)
559 {
560     connect(item, &QQuickItem::childrenRectChanged, this, &AbstractScreenGrabber::updateOverlay);
561     connect(item, &QQuickItem::rotationChanged, this, &AbstractScreenGrabber::updateOverlay);
562     connect(item, &QQuickItem::scaleChanged, this, &AbstractScreenGrabber::updateOverlay);
563     connect(item, &QQuickItem::widthChanged, this, &AbstractScreenGrabber::updateOverlay);
564     connect(item, &QQuickItem::heightChanged, this, &AbstractScreenGrabber::updateOverlay);
565 }
566 
disconnectTopItemChanges(QQuickItem * item)567 void AbstractScreenGrabber::disconnectTopItemChanges(QQuickItem *item)
568 {
569     disconnect(item, &QQuickItem::childrenRectChanged, this, &AbstractScreenGrabber::updateOverlay);
570     disconnect(item, &QQuickItem::rotationChanged, this, &AbstractScreenGrabber::updateOverlay);
571     disconnect(item, &QQuickItem::scaleChanged, this, &AbstractScreenGrabber::updateOverlay);
572     disconnect(item, &QQuickItem::widthChanged, this, &AbstractScreenGrabber::updateOverlay);
573     disconnect(item, &QQuickItem::heightChanged, this, &AbstractScreenGrabber::updateOverlay);
574 }
575 
576 #ifndef QT_NO_OPENGL
OpenGLScreenGrabber(QQuickWindow * window)577 OpenGLScreenGrabber::OpenGLScreenGrabber(QQuickWindow *window)
578     : AbstractScreenGrabber(window)
579     , m_isGrabbing(false)
580 {
581     // Force DirectConnection else Auto lead to Queued which is not good.
582     connect(m_window.data(), &QQuickWindow::afterSynchronizing,
583             this, &OpenGLScreenGrabber::windowAfterSynchronizing, Qt::DirectConnection);
584     connect(m_window.data(), &QQuickWindow::afterRendering,
585             this, &OpenGLScreenGrabber::windowAfterRendering, Qt::DirectConnection);
586 }
587 
588 OpenGLScreenGrabber::~OpenGLScreenGrabber() = default;
589 
requestGrabWindow(const QRectF & userViewport)590 void OpenGLScreenGrabber::requestGrabWindow(const QRectF &userViewport)
591 {
592     setGrabbingMode(true, userViewport);
593 }
594 
setGrabbingMode(bool isGrabbing,const QRectF & userViewport)595 void OpenGLScreenGrabber::setGrabbingMode(bool isGrabbing, const QRectF &userViewport)
596 {
597     QMutexLocker locker(&m_mutex);
598 
599     if (m_isGrabbing == isGrabbing)
600         return;
601 
602     m_isGrabbing = isGrabbing;
603     m_userViewport = userViewport;
604 
605     emit grabberReadyChanged(!m_isGrabbing);
606 
607     if (m_isGrabbing)
608         updateOverlay();
609 }
610 
windowAfterSynchronizing()611 void OpenGLScreenGrabber::windowAfterSynchronizing()
612 {
613     // We are in the rendering thread at this point
614     // And the gui thread is locked
615     gatherRenderInfo();
616 }
617 
windowAfterRendering()618 void OpenGLScreenGrabber::windowAfterRendering()
619 {
620     QMutexLocker locker(&m_mutex);
621 
622     // We are in the rendering thread at this point
623     // And the gui thread is NOT locked
624     Q_ASSERT(QOpenGLContext::currentContext() == m_window->openglContext());
625 
626     if (m_isGrabbing) {
627         const auto window = QRectF(QPoint(0,0), m_renderInfo.windowSize);
628         const auto intersect = m_userViewport.isValid() ? window.intersected(m_userViewport) : window;
629 
630         // readout parameters
631         // when in doubt, round x and y to floor--> reads one pixel more
632         const int x = static_cast<int>(std::floor(intersect.x() * m_renderInfo.dpr));
633         // correct y for gpu-flipped textures being read from the bottom
634         const int y = static_cast<int>(std::floor((m_renderInfo.windowSize.height() - intersect.height() - intersect.y()) * m_renderInfo.dpr));
635         // when in doubt, round up w and h --> also reads one pixel more
636         int w = static_cast<int>(std::ceil(intersect.width() * m_renderInfo.dpr));
637         int h = static_cast<int>(std::ceil(intersect.height() * m_renderInfo.dpr));
638 
639         // cap to viewport size (which we can overshoot due to rounding errors in highdpi scaling)
640         QOpenGLFunctions *glFuncs = QOpenGLContext::currentContext()->functions();
641         int viewport[4];
642         glFuncs->glGetIntegerv(GL_VIEWPORT, viewport);
643         if (x + w > viewport[2])
644             w = viewport[2] - x;
645         if (y + h > viewport[3])
646             h = viewport[3] - y;
647 
648         m_grabbedFrame.transform.reset();
649 
650         if (m_grabbedFrame.image.size() != QSize(w, h))
651             m_grabbedFrame.image = QImage(w, h, QImage::Format_RGBA8888);
652 
653         glFuncs->glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_grabbedFrame.image.bits());
654 
655         // set transform to flip the read texture later, when displayed
656         // Keep in mind that transforms are local coordinate (ie, not impacted by the device pixel ratio)
657         m_grabbedFrame.transform.scale(1.0, -1.0);
658         m_grabbedFrame.transform.translate(intersect.x() , -intersect.y() - intersect.height());
659         m_grabbedFrame.image.setDevicePixelRatio(m_renderInfo.dpr);
660 
661         // Let emit the signal even if our image is possibly null, this way we make perfect ping/pong
662         // requests making it easier to unit test.
663         emit sceneGrabbed(m_grabbedFrame);
664     }
665 
666     drawDecorations();
667 
668     m_window->resetOpenGLState();
669 
670     if (m_isGrabbing) {
671         locker.unlock();
672         setGrabbingMode(false, QRectF());
673     } else {
674         emit sceneChanged();
675     }
676 }
677 
drawDecorations()678 void OpenGLScreenGrabber::drawDecorations()
679 {
680     // We are in the rendering thread at this point
681     // And the gui thread is NOT locked
682     QOpenGLPaintDevice device(m_renderInfo.windowSize * m_renderInfo.dpr);
683     device.setDevicePixelRatio(m_renderInfo.dpr);
684     QPainter p(&device);
685     doDrawDecorations(p);
686 }
687 #endif
688 
689 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
SoftwareScreenGrabber(QQuickWindow * window)690 SoftwareScreenGrabber::SoftwareScreenGrabber(QQuickWindow* window)
691     : AbstractScreenGrabber(window)
692 {
693     connect(m_window.data(), &QQuickWindow::afterRendering,
694             this, &SoftwareScreenGrabber::windowAfterRendering, Qt::DirectConnection);
695     connect(m_window.data(), &QQuickWindow::beforeRendering,
696             this, &SoftwareScreenGrabber::windowBeforeRendering, Qt::DirectConnection);
697 }
698 
699 SoftwareScreenGrabber::~SoftwareScreenGrabber() = default;
700 
updateOverlay()701 void SoftwareScreenGrabber::updateOverlay()
702 {
703     if (m_window) {
704         if (!m_currentItem.isNull())
705             Q_ASSERT(m_currentItem.item()->window() == m_window);
706 
707 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 3)
708         auto renderer = softwareRenderer();
709         if (renderer)
710             renderer->markDirty();
711 #endif
712 
713         m_window->update();
714     }
715 }
716 
requestGrabWindow(const QRectF & userViewport)717 void SoftwareScreenGrabber::requestGrabWindow(const QRectF& userViewport)
718 {
719     Q_UNUSED(userViewport);
720 
721     m_isGrabbing = true;
722     qreal dpr = 1.0;
723     // See QTBUG-53795
724     dpr = m_window->effectiveDevicePixelRatio();
725 
726 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 3)
727     m_grabbedFrame.image = QImage(m_window->size() * dpr, QImage::Format_ARGB32_Premultiplied);
728     m_grabbedFrame.image.setDevicePixelRatio(dpr);
729     m_grabbedFrame.image.fill(Qt::white);
730 
731     QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(m_window);
732     QSGSoftwareRenderer *renderer = softwareRenderer();
733     if (!renderer)
734         return;
735 
736     QPaintDevice *regularRenderDevice = renderer->currentPaintDevice();
737     renderer->setCurrentPaintDevice(&m_grabbedFrame.image);
738     renderer->markDirty();
739     winPriv->polishItems();
740     winPriv->syncSceneGraph();
741     winPriv->renderSceneGraph(m_window->size());
742     renderer->setCurrentPaintDevice(regularRenderDevice);
743 #else
744     m_grabbedFrame.image = m_window->grabWindow();
745     m_grabbedFrame.image.setDevicePixelRatio(dpr);
746 #endif
747 
748     m_isGrabbing = false;
749 
750     emit sceneGrabbed(m_grabbedFrame);
751 }
752 
drawDecorations()753 void SoftwareScreenGrabber::drawDecorations()
754 {
755 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 3)
756     auto renderer = softwareRenderer();
757     if (!renderer)
758         return;
759     QPainter p(renderer->currentPaintDevice());
760     p.setClipRegion(renderer->flushRegion());
761     doDrawDecorations(p);
762 #endif
763 }
764 
windowBeforeRendering()765 void SoftwareScreenGrabber::windowBeforeRendering()
766 {
767 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 3)
768     QuickItemGeometry oldItemRect = m_grabbedFrame.itemsGeometry.size() ? m_grabbedFrame.itemsGeometry.front() : QuickItemGeometry(); // So far the vector never has more than one element...
769     gatherRenderInfo();
770     QuickItemGeometry newItemRect = m_grabbedFrame.itemsGeometry.size() ? m_grabbedFrame.itemsGeometry.front() : QuickItemGeometry();
771     if (m_decorationsEnabled && newItemRect != oldItemRect) {
772         // The item's scene coordinates can change unrecognizedly. If they do, we need a
773         // full window repaint though, for the overlay to be correct.
774         softwareRenderer()->markDirty();
775     }
776 #endif
777 }
778 
windowAfterRendering()779 void SoftwareScreenGrabber::windowAfterRendering()
780 {
781 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 3)
782     if (!m_isGrabbing) {
783         drawDecorations();
784         emit sceneChanged();
785     } else {
786         m_isGrabbing = false;
787     }
788 #endif
789 }
790 
softwareRenderer() const791 QSGSoftwareRenderer *SoftwareScreenGrabber::softwareRenderer() const
792 {
793     QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(m_window);
794     if (!winPriv)
795         return nullptr;
796     QSGSoftwareRenderer *softwareRenderer = dynamic_cast<QSGSoftwareRenderer*>(winPriv->renderer);
797     return softwareRenderer;
798 }
799 #endif
800 
801 
802