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