You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
19 KiB
604 lines
19 KiB
/* |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "effect/offscreenquickview.h" |
|
#include "effect/effecthandler.h" |
|
|
|
#include "logging_p.h" |
|
#include "opengl/glutils.h" |
|
#include "opengl/openglcontext.h" |
|
|
|
#include <QGuiApplication> |
|
#include <QQmlComponent> |
|
#include <QQmlContext> |
|
#include <QQmlEngine> |
|
#include <QQuickItem> |
|
#include <QQuickRenderControl> |
|
#include <QQuickView> |
|
#include <QStyleHints> |
|
|
|
#include <QOffscreenSurface> |
|
#include <QOpenGLContext> |
|
#include <QOpenGLFramebufferObject> |
|
#include <QQuickGraphicsDevice> |
|
#include <QQuickOpenGLUtils> |
|
#include <QQuickRenderTarget> |
|
#include <QTimer> |
|
#include <private/qeventpoint_p.h> // for QMutableEventPoint |
|
|
|
namespace KWin |
|
{ |
|
|
|
class Q_DECL_HIDDEN OffscreenQuickView::Private |
|
{ |
|
public: |
|
std::unique_ptr<QQuickWindow> m_view; |
|
std::unique_ptr<QQuickRenderControl> m_renderControl; |
|
std::unique_ptr<QOffscreenSurface> m_offscreenSurface; |
|
std::unique_ptr<QOpenGLContext> m_glcontext; |
|
std::unique_ptr<QOpenGLFramebufferObject> m_fbo; |
|
|
|
std::unique_ptr<QTimer> m_repaintTimer; |
|
QImage m_image; |
|
std::unique_ptr<GLTexture> m_textureExport; |
|
// if we should capture a QImage after rendering into our BO. |
|
// Used for either software QtQuick rendering and nonGL kwin rendering |
|
bool m_useBlit = false; |
|
bool m_visible = true; |
|
bool m_hasAlphaChannel = true; |
|
bool m_automaticRepaint = true; |
|
|
|
QList<QEventPoint> touchPoints; |
|
QPointingDevice *touchDevice; |
|
|
|
ulong lastMousePressTime = 0; |
|
Qt::MouseButton lastMousePressButton = Qt::NoButton; |
|
|
|
void releaseResources(); |
|
|
|
void updateTouchState(Qt::TouchPointState state, qint32 id, const QPointF &pos); |
|
}; |
|
|
|
class Q_DECL_HIDDEN OffscreenQuickScene::Private |
|
{ |
|
public: |
|
Private() |
|
{ |
|
} |
|
|
|
std::unique_ptr<QQmlComponent> qmlComponent; |
|
std::unique_ptr<QQuickItem> quickItem; |
|
}; |
|
|
|
OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha) |
|
: d(new OffscreenQuickView::Private) |
|
{ |
|
d->m_renderControl = std::make_unique<QQuickRenderControl>(); |
|
|
|
d->m_view = std::make_unique<QQuickWindow>(d->m_renderControl.get()); |
|
Q_ASSERT(d->m_view->setProperty("_KWIN_WINDOW_IS_OFFSCREEN", true) || true); |
|
d->m_view->setFlags(Qt::FramelessWindowHint); |
|
d->m_view->setColor(Qt::transparent); |
|
|
|
d->m_hasAlphaChannel = alpha; |
|
if (exportMode == ExportMode::Image) { |
|
d->m_useBlit = true; |
|
} |
|
|
|
const bool usingGl = d->m_view->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL; |
|
|
|
if (!usingGl) { |
|
qCDebug(LIBKWINEFFECTS) << "QtQuick Software rendering mode detected"; |
|
d->m_useBlit = true; |
|
// explicilty do not call QQuickRenderControl::initialize, see Qt docs |
|
} else { |
|
QSurfaceFormat format; |
|
format.setOption(QSurfaceFormat::ResetNotification); |
|
format.setDepthBufferSize(16); |
|
format.setStencilBufferSize(8); |
|
if (alpha) { |
|
format.setAlphaBufferSize(8); |
|
} |
|
|
|
d->m_view->setFormat(format); |
|
|
|
auto shareContext = QOpenGLContext::globalShareContext(); |
|
d->m_glcontext = std::make_unique<QOpenGLContext>(); |
|
d->m_glcontext->setShareContext(shareContext); |
|
d->m_glcontext->setFormat(format); |
|
d->m_glcontext->create(); |
|
|
|
// and the offscreen surface |
|
d->m_offscreenSurface = std::make_unique<QOffscreenSurface>(); |
|
d->m_offscreenSurface->setFormat(d->m_glcontext->format()); |
|
d->m_offscreenSurface->create(); |
|
|
|
d->m_glcontext->makeCurrent(d->m_offscreenSurface.get()); |
|
d->m_view->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(d->m_glcontext.get())); |
|
d->m_renderControl->initialize(); |
|
d->m_glcontext->doneCurrent(); |
|
|
|
// On Wayland, contexts are implicitly shared and QOpenGLContext::globalShareContext() is null. |
|
if (shareContext && !d->m_glcontext->shareContext()) { |
|
qCDebug(LIBKWINEFFECTS) << "Failed to create a shared context, falling back to raster rendering"; |
|
// still render via GL, but blit for presentation |
|
d->m_useBlit = true; |
|
} |
|
} |
|
|
|
auto updateSize = [this]() { |
|
contentItem()->setSize(d->m_view->size()); |
|
}; |
|
updateSize(); |
|
connect(d->m_view.get(), &QWindow::widthChanged, this, updateSize); |
|
connect(d->m_view.get(), &QWindow::heightChanged, this, updateSize); |
|
|
|
d->m_repaintTimer = std::make_unique<QTimer>(); |
|
d->m_repaintTimer->setSingleShot(true); |
|
d->m_repaintTimer->setInterval(10); |
|
|
|
connect(d->m_repaintTimer.get(), &QTimer::timeout, this, &OffscreenQuickView::update); |
|
connect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested, this, &OffscreenQuickView::handleRenderRequested); |
|
connect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged, this, &OffscreenQuickView::handleSceneChanged); |
|
|
|
d->touchDevice = new QPointingDevice(QStringLiteral("ForwardingTouchDevice"), {}, QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position, 10, {}); |
|
} |
|
|
|
OffscreenQuickView::~OffscreenQuickView() |
|
{ |
|
disconnect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested, this, &OffscreenQuickView::handleRenderRequested); |
|
disconnect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged, this, &OffscreenQuickView::handleSceneChanged); |
|
|
|
if (d->m_glcontext) { |
|
// close the view whilst we have an active GL context |
|
d->m_glcontext->makeCurrent(d->m_offscreenSurface.get()); |
|
} |
|
|
|
d->m_view.reset(); |
|
d->m_renderControl.reset(); |
|
} |
|
|
|
bool OffscreenQuickView::automaticRepaint() const |
|
{ |
|
return d->m_automaticRepaint; |
|
} |
|
|
|
void OffscreenQuickView::setAutomaticRepaint(bool set) |
|
{ |
|
if (d->m_automaticRepaint != set) { |
|
d->m_automaticRepaint = set; |
|
|
|
// If there's an in-flight update, disable it. |
|
if (!d->m_automaticRepaint) { |
|
d->m_repaintTimer->stop(); |
|
} |
|
} |
|
} |
|
|
|
void OffscreenQuickView::handleSceneChanged() |
|
{ |
|
if (d->m_automaticRepaint) { |
|
d->m_repaintTimer->start(); |
|
} |
|
Q_EMIT sceneChanged(); |
|
} |
|
|
|
void OffscreenQuickView::handleRenderRequested() |
|
{ |
|
if (d->m_automaticRepaint) { |
|
d->m_repaintTimer->start(); |
|
} |
|
Q_EMIT renderRequested(); |
|
} |
|
|
|
void OffscreenQuickView::update() |
|
{ |
|
if (!d->m_visible) { |
|
return; |
|
} |
|
if (d->m_view->size().isEmpty()) { |
|
return; |
|
} |
|
|
|
bool usingGl = d->m_glcontext != nullptr; |
|
OpenGlContext *previousContext = OpenGlContext::currentContext(); |
|
|
|
if (usingGl) { |
|
if (!d->m_glcontext->makeCurrent(d->m_offscreenSurface.get())) { |
|
// probably a context loss event, kwin is about to reset all the effects anyway |
|
return; |
|
} |
|
|
|
const QSize nativeSize = d->m_view->size() * d->m_view->devicePixelRatio(); |
|
if (!d->m_fbo || d->m_fbo->size() != nativeSize) { |
|
d->m_textureExport.reset(nullptr); |
|
|
|
QOpenGLFramebufferObjectFormat fboFormat; |
|
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
|
fboFormat.setInternalTextureFormat(GL_RGBA8); |
|
|
|
d->m_fbo = std::make_unique<QOpenGLFramebufferObject>(nativeSize, fboFormat); |
|
if (!d->m_fbo->isValid()) { |
|
d->m_fbo.reset(); |
|
d->m_glcontext->doneCurrent(); |
|
return; |
|
} |
|
} |
|
|
|
QQuickRenderTarget renderTarget = QQuickRenderTarget::fromOpenGLTexture(d->m_fbo->texture(), d->m_fbo->size()); |
|
renderTarget.setDevicePixelRatio(d->m_view->devicePixelRatio()); |
|
|
|
d->m_view->setRenderTarget(renderTarget); |
|
} |
|
|
|
d->m_renderControl->polishItems(); |
|
if (usingGl) { |
|
d->m_renderControl->beginFrame(); |
|
} |
|
d->m_renderControl->sync(); |
|
d->m_renderControl->render(); |
|
if (usingGl) { |
|
d->m_renderControl->endFrame(); |
|
} |
|
|
|
if (usingGl) { |
|
QQuickOpenGLUtils::resetOpenGLState(); |
|
} |
|
|
|
if (d->m_useBlit) { |
|
if (usingGl) { |
|
d->m_image = d->m_fbo->toImage(); |
|
d->m_image.setDevicePixelRatio(d->m_view->devicePixelRatio()); |
|
} else { |
|
d->m_image = d->m_view->grabWindow(); |
|
} |
|
} |
|
|
|
if (usingGl) { |
|
QOpenGLFramebufferObject::bindDefault(); |
|
d->m_glcontext->doneCurrent(); |
|
if (previousContext) { |
|
previousContext->makeCurrent(); |
|
} |
|
} |
|
Q_EMIT repaintNeeded(); |
|
} |
|
|
|
void OffscreenQuickView::forwardMouseEvent(QEvent *e) |
|
{ |
|
if (!d->m_visible) { |
|
return; |
|
} |
|
switch (e->type()) { |
|
case QEvent::MouseMove: |
|
case QEvent::MouseButtonPress: |
|
case QEvent::MouseButtonRelease: { |
|
QMouseEvent *me = static_cast<QMouseEvent *>(e); |
|
const QPoint widgetPos = d->m_view->mapFromGlobal(me->pos()); |
|
QMouseEvent cloneEvent(me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers()); |
|
cloneEvent.setAccepted(false); |
|
QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent); |
|
e->setAccepted(cloneEvent.isAccepted()); |
|
|
|
if (e->type() == QEvent::MouseButtonPress) { |
|
const ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval()); |
|
const bool doubleClick = (me->timestamp() - d->lastMousePressTime < doubleClickInterval) && me->button() == d->lastMousePressButton; |
|
d->lastMousePressTime = me->timestamp(); |
|
d->lastMousePressButton = me->button(); |
|
if (doubleClick) { |
|
d->lastMousePressButton = Qt::NoButton; |
|
QMouseEvent doubleClickEvent(QEvent::MouseButtonDblClick, me->localPos(), me->windowPos(), me->screenPos(), me->button(), me->buttons(), me->modifiers()); |
|
QCoreApplication::sendEvent(d->m_view.get(), &doubleClickEvent); |
|
} |
|
} |
|
|
|
return; |
|
} |
|
case QEvent::HoverEnter: |
|
case QEvent::HoverLeave: |
|
case QEvent::HoverMove: { |
|
QHoverEvent *he = static_cast<QHoverEvent *>(e); |
|
const QPointF widgetPos = d->m_view->mapFromGlobal(he->pos()); |
|
const QPointF oldWidgetPos = d->m_view->mapFromGlobal(he->oldPos()); |
|
QHoverEvent cloneEvent(he->type(), widgetPos, oldWidgetPos, he->modifiers()); |
|
cloneEvent.setAccepted(false); |
|
QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent); |
|
e->setAccepted(cloneEvent.isAccepted()); |
|
return; |
|
} |
|
case QEvent::Wheel: { |
|
QWheelEvent *we = static_cast<QWheelEvent *>(e); |
|
const QPointF widgetPos = d->m_view->mapFromGlobal(we->position().toPoint()); |
|
QWheelEvent cloneEvent(widgetPos, we->globalPosition(), we->pixelDelta(), we->angleDelta(), we->buttons(), |
|
we->modifiers(), we->phase(), we->inverted()); |
|
cloneEvent.setAccepted(false); |
|
QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent); |
|
e->setAccepted(cloneEvent.isAccepted()); |
|
return; |
|
} |
|
default: |
|
return; |
|
} |
|
} |
|
|
|
void OffscreenQuickView::forwardKeyEvent(QKeyEvent *keyEvent) |
|
{ |
|
if (!d->m_visible) { |
|
return; |
|
} |
|
QCoreApplication::sendEvent(d->m_view.get(), keyEvent); |
|
} |
|
|
|
bool OffscreenQuickView::forwardTouchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time) |
|
{ |
|
d->updateTouchState(Qt::TouchPointPressed, id, pos); |
|
|
|
QTouchEvent event(QEvent::TouchBegin, d->touchDevice, Qt::NoModifier, d->touchPoints); |
|
event.setTimestamp(std::chrono::duration_cast<std::chrono::milliseconds>(time).count()); |
|
event.setAccepted(false); |
|
QCoreApplication::sendEvent(d->m_view.get(), &event); |
|
|
|
return event.isAccepted(); |
|
} |
|
|
|
bool OffscreenQuickView::forwardTouchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time) |
|
{ |
|
d->updateTouchState(Qt::TouchPointMoved, id, pos); |
|
|
|
QTouchEvent event(QEvent::TouchUpdate, d->touchDevice, Qt::NoModifier, d->touchPoints); |
|
event.setTimestamp(std::chrono::duration_cast<std::chrono::milliseconds>(time).count()); |
|
event.setAccepted(false); |
|
QCoreApplication::sendEvent(d->m_view.get(), &event); |
|
|
|
return event.isAccepted(); |
|
} |
|
|
|
bool OffscreenQuickView::forwardTouchUp(qint32 id, std::chrono::microseconds time) |
|
{ |
|
d->updateTouchState(Qt::TouchPointReleased, id, QPointF{}); |
|
|
|
QTouchEvent event(QEvent::TouchEnd, d->touchDevice, Qt::NoModifier, d->touchPoints); |
|
event.setTimestamp(std::chrono::duration_cast<std::chrono::milliseconds>(time).count()); |
|
event.setAccepted(false); |
|
QCoreApplication::sendEvent(d->m_view.get(), &event); |
|
|
|
return event.isAccepted(); |
|
} |
|
|
|
QRect OffscreenQuickView::geometry() const |
|
{ |
|
return d->m_view->geometry(); |
|
} |
|
|
|
void OffscreenQuickView::setOpacity(qreal opacity) |
|
{ |
|
d->m_view->setOpacity(opacity); |
|
} |
|
|
|
qreal OffscreenQuickView::opacity() const |
|
{ |
|
return d->m_view->opacity(); |
|
} |
|
|
|
bool OffscreenQuickView::hasAlphaChannel() const |
|
{ |
|
return d->m_hasAlphaChannel; |
|
} |
|
|
|
QQuickItem *OffscreenQuickView::contentItem() const |
|
{ |
|
return d->m_view->contentItem(); |
|
} |
|
|
|
QQuickWindow *OffscreenQuickView::window() const |
|
{ |
|
return d->m_view.get(); |
|
} |
|
|
|
void OffscreenQuickView::setVisible(bool visible) |
|
{ |
|
if (d->m_visible == visible) { |
|
return; |
|
} |
|
d->m_visible = visible; |
|
|
|
if (visible) { |
|
Q_EMIT d->m_renderControl->renderRequested(); |
|
} else { |
|
// deferred to not change GL context |
|
QTimer::singleShot(0, this, [this]() { |
|
d->releaseResources(); |
|
}); |
|
} |
|
} |
|
|
|
bool OffscreenQuickView::isVisible() const |
|
{ |
|
return d->m_visible; |
|
} |
|
|
|
void OffscreenQuickView::show() |
|
{ |
|
setVisible(true); |
|
} |
|
|
|
void OffscreenQuickView::hide() |
|
{ |
|
setVisible(false); |
|
} |
|
|
|
GLTexture *OffscreenQuickView::bufferAsTexture() |
|
{ |
|
if (d->m_useBlit) { |
|
d->m_textureExport = GLTexture::upload(d->m_image); |
|
} else { |
|
if (!d->m_fbo) { |
|
return nullptr; |
|
} |
|
if (!d->m_textureExport) { |
|
d->m_textureExport = GLTexture::createNonOwningWrapper(d->m_fbo->texture(), d->m_fbo->format().internalTextureFormat(), d->m_fbo->size()); |
|
} |
|
} |
|
return d->m_textureExport.get(); |
|
} |
|
|
|
QImage OffscreenQuickView::bufferAsImage() const |
|
{ |
|
return d->m_image; |
|
} |
|
|
|
QSize OffscreenQuickView::size() const |
|
{ |
|
return d->m_view->geometry().size(); |
|
} |
|
|
|
void OffscreenQuickView::setGeometry(const QRect &rect) |
|
{ |
|
const QRect oldGeometry = d->m_view->geometry(); |
|
d->m_view->setGeometry(rect); |
|
// QWindow::setGeometry() won't sync output if there's no platform window. |
|
d->m_view->setScreen(QGuiApplication::screenAt(rect.center())); |
|
Q_EMIT geometryChanged(oldGeometry, rect); |
|
} |
|
|
|
void OffscreenQuickView::Private::releaseResources() |
|
{ |
|
if (m_glcontext) { |
|
m_glcontext->makeCurrent(m_offscreenSurface.get()); |
|
m_view->releaseResources(); |
|
m_glcontext->doneCurrent(); |
|
} else { |
|
m_view->releaseResources(); |
|
} |
|
} |
|
|
|
void OffscreenQuickView::Private::updateTouchState(Qt::TouchPointState state, qint32 id, const QPointF &pos) |
|
{ |
|
// Remove the points that were previously in a released state, since they |
|
// are no longer relevant. Additionally, reset the state of all remaining |
|
// points to Stationary so we only have one touch point with a different |
|
// state. |
|
touchPoints.erase(std::remove_if(touchPoints.begin(), touchPoints.end(), [](QTouchEvent::TouchPoint &point) { |
|
if (point.state() == QEventPoint::Released) { |
|
return true; |
|
} |
|
QMutableEventPoint::setState(point, QEventPoint::Stationary); |
|
return false; |
|
}), |
|
touchPoints.end()); |
|
|
|
// QtQuick Pointer Handlers incorrectly consider a touch point with ID 0 |
|
// to be an invalid touch point. This has been fixed in Qt 6 but could not |
|
// be fixed for Qt 5. Instead, we offset kwin's internal IDs with this |
|
// offset to trick QtQuick into treating them as valid points. |
|
static const qint32 idOffset = 111; |
|
|
|
// Find the touch point that has changed. This is separate from the above |
|
// loop because removing the released touch points invalidates iterators. |
|
auto changed = std::find_if(touchPoints.begin(), touchPoints.end(), [id](const QTouchEvent::TouchPoint &point) { |
|
return point.id() == id + idOffset; |
|
}); |
|
|
|
switch (state) { |
|
case Qt::TouchPointPressed: { |
|
if (changed != touchPoints.end()) { |
|
return; |
|
} |
|
|
|
QTouchEvent::TouchPoint point; |
|
QMutableEventPoint::setState(point, QEventPoint::Pressed); |
|
QMutableEventPoint::setId(point, id + idOffset); |
|
QMutableEventPoint::setGlobalPosition(point, pos); |
|
QMutableEventPoint::setScenePosition(point, m_view->mapFromGlobal(pos.toPoint())); |
|
QMutableEventPoint::setPosition(point, m_view->mapFromGlobal(pos.toPoint())); |
|
|
|
touchPoints.append(point); |
|
} break; |
|
case Qt::TouchPointMoved: { |
|
if (changed == touchPoints.end()) { |
|
return; |
|
} |
|
|
|
auto &point = *changed; |
|
QMutableEventPoint::setGlobalLastPosition(point, point.globalPosition()); |
|
QMutableEventPoint::setState(point, QEventPoint::Updated); |
|
QMutableEventPoint::setScenePosition(point, m_view->mapFromGlobal(pos.toPoint())); |
|
QMutableEventPoint::setPosition(point, m_view->mapFromGlobal(pos.toPoint())); |
|
QMutableEventPoint::setGlobalPosition(point, pos); |
|
} break; |
|
case Qt::TouchPointReleased: { |
|
if (changed == touchPoints.end()) { |
|
return; |
|
} |
|
|
|
auto &point = *changed; |
|
QMutableEventPoint::setGlobalLastPosition(point, point.globalPosition()); |
|
QMutableEventPoint::setState(point, QEventPoint::Released); |
|
} break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
OffscreenQuickScene::OffscreenQuickScene(OffscreenQuickView::ExportMode exportMode, bool alpha) |
|
: OffscreenQuickView(exportMode, alpha) |
|
, d(new OffscreenQuickScene::Private) |
|
{ |
|
} |
|
|
|
OffscreenQuickScene::~OffscreenQuickScene() = default; |
|
|
|
void OffscreenQuickScene::setSource(const QUrl &source) |
|
{ |
|
setSource(source, QVariantMap()); |
|
} |
|
|
|
void OffscreenQuickScene::setSource(const QUrl &source, const QVariantMap &initialProperties) |
|
{ |
|
if (!d->qmlComponent) { |
|
d->qmlComponent = std::make_unique<QQmlComponent>(effects->qmlEngine()); |
|
} |
|
|
|
d->qmlComponent->loadUrl(source); |
|
if (d->qmlComponent->isError()) { |
|
qCWarning(LIBKWINEFFECTS).nospace() << "Failed to load effect quick view " << source << ": " << d->qmlComponent->errors(); |
|
d->qmlComponent.reset(); |
|
return; |
|
} |
|
|
|
d->quickItem.reset(); |
|
|
|
std::unique_ptr<QObject> qmlObject(d->qmlComponent->createWithInitialProperties(initialProperties)); |
|
QQuickItem *item = qobject_cast<QQuickItem *>(qmlObject.get()); |
|
if (!item) { |
|
qCWarning(LIBKWINEFFECTS) << "Root object of effect quick view" << source << "is not a QQuickItem"; |
|
return; |
|
} |
|
|
|
qmlObject.release(); |
|
d->quickItem.reset(item); |
|
|
|
item->setParentItem(contentItem()); |
|
|
|
auto updateSize = [item, this]() { |
|
item->setSize(contentItem()->size()); |
|
}; |
|
updateSize(); |
|
connect(contentItem(), &QQuickItem::widthChanged, item, updateSize); |
|
connect(contentItem(), &QQuickItem::heightChanged, item, updateSize); |
|
} |
|
|
|
QQuickItem *OffscreenQuickScene::rootItem() const |
|
{ |
|
return d->quickItem.get(); |
|
} |
|
|
|
} // namespace KWin |
|
|
|
#include "moc_offscreenquickview.cpp"
|
|
|