Revision control
++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qwaylandquickitem.h"
++ #include "qwaylandquickitem_p.h"
++ #include "qwaylandquicksurface.h"
++ #include "qwaylandinputmethodcontrol.h"
++ #include "qwaylandtextinput.h"
++ #include "qwaylandtextinputv3.h"
++ #include "qwaylandqttextinputmethod.h"
++ #include "qwaylandquickoutput.h"
++ #include <QtWaylandCompositor/qwaylandcompositor.h>
++ #include <QtWaylandCompositor/qwaylandseat.h>
++ #include <QtWaylandCompositor/qwaylandbufferref.h>
++ #if QT_CONFIG(draganddrop)
++ #include <QtWaylandCompositor/QWaylandDrag>
++ #endif
++ #include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
++ #include <QtWaylandCompositor/private/qwaylandsurface_p.h>
++
++ #if QT_CONFIG(opengl)
++ # include <QtOpenGL/QOpenGLTexture>
++ # include <QtGui/QOpenGLFunctions>
++ #endif
++
++ #include <QtGui/QKeyEvent>
++ #include <QtGui/QGuiApplication>
++ #include <QtGui/QScreen>
++
++ #include <QtQuick/QSGSimpleTextureNode>
++ #include <QtQuick/QQuickWindow>
++ #include <QtQuick/qsgtexture.h>
++
++ #include <QtCore/QFile>
++ #include <QtCore/QMutexLocker>
++ #include <QtCore/QMutex>
++
++ #include <wayland-server-core.h>
++ #include <QThread>
++
++ #if QT_CONFIG(opengl)
++ #include <QtGui/private/qshaderdescription_p.h>
++ #endif
++
++ #ifndef GL_TEXTURE_EXTERNAL_OES
++ #define GL_TEXTURE_EXTERNAL_OES 0x8D65
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ #if QT_CONFIG(opengl)
++ static const struct {
++ const char * const vertexShaderSourceFile;
++ const char * const fragmentShaderSourceFile;
++ int planeCount;
++ bool canProvideTexture;
++ QSGMaterial::Flags materialFlags;
++ QSGMaterialType materialType;
++ } bufferTypes[] = {
++ // BufferFormatEgl_Null
++ { "", "", 0, false, {}, {} },
++
++ // BufferFormatEgl_RGB (GL_TEXTURE_2D)
++ {
++ ":/qt-project.org/wayland/compositor/shaders/surface.vert.qsb",
++ ":/qt-project.org/wayland/compositor/shaders/surface_rgbx.frag.qsb",
++ 1, true,
++ QSGMaterial::Blending,
++ {}
++ },
++
++ // BufferFormatEgl_RGBA (GL_TEXTURE_2D)
++ {
++ ":/qt-project.org/wayland/compositor/shaders/surface.vert.qsb",
++ ":/qt-project.org/wayland/compositor/shaders/surface_rgba.frag.qsb",
++ 1, true,
++ QSGMaterial::Blending,
++ {}
++ },
++
++ // BufferFormatEgl_EXTERNAL_OES (GL_TEXTURE_EXTERNAL_OES)
++ {
++ ":/qt-project.org/wayland/compositor/shaders/surface.vert.qsb",
++ ":/qt-project.org/wayland/compositor/shaders/surface_oes_external.frag",
++ 1, false,
++ QSGMaterial::Blending,
++ {}
++ },
++
++ // BufferFormatEgl_Y_U_V (GL_TEXTURE_2D)
++ {
++ ":/qt-project.org/wayland/compositor/shaders/surface.vert.qsb",
++ ":/qt-project.org/wayland/compositor/shaders/surface_y_u_v.frag.qsb",
++ 3, false,
++ QSGMaterial::Blending,
++ {}
++ },
++
++ // BufferFormatEgl_Y_UV (GL_TEXTURE_2D)
++ {
++ ":/qt-project.org/wayland/compositor/shaders/surface.vert.qsb",
++ ":/qt-project.org/wayland/compositor/shaders/surface_y_uv.frag.qsb",
++ 2, false,
++ QSGMaterial::Blending,
++ {}
++ },
++
++ // BufferFormatEgl_Y_XUXV (GL_TEXTURE_2D)
++ {
++ ":/qt-project.org/wayland/compositor/shaders/surface.vert.qsb",
++ ":/qt-project.org/wayland/compositor/shaders/surface_y_xuxv.frag.qsb",
++ 2, false,
++ QSGMaterial::Blending,
++ {}
++ }
++ };
++
++ QWaylandBufferMaterialShader::QWaylandBufferMaterialShader(QWaylandBufferRef::BufferFormatEgl format)
++ {
++ Q_UNUSED(format);
++ setShaderFileName(VertexStage, QString::fromLatin1(bufferTypes[format].vertexShaderSourceFile));
++ auto fragmentShaderSourceFile = QString::fromLatin1(bufferTypes[format].fragmentShaderSourceFile);
++
++ if (format == QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES)
++ setupExternalOESShader(fragmentShaderSourceFile);
++ else
++ setShaderFileName(FragmentStage, fragmentShaderSourceFile);
++ }
++
++ void QWaylandBufferMaterialShader::setupExternalOESShader(const QString &shaderFilename)
++ {
++ #if QT_CONFIG(opengl)
++ QFile shaderFile(shaderFilename);
++ if (!shaderFile.open(QIODevice::ReadOnly)) {
++ qCWarning(qLcWaylandCompositor) << "Cannot find external OES shader file:" << shaderFilename;
++ return;
++ }
++ QByteArray FS = shaderFile.readAll();
++
++ static const char *FS_GLES_PREAMBLE =
++ "#extension GL_OES_EGL_image_external : require\n"
++ "precision highp float;\n";
++ static const char *FS_GL_PREAMBLE =
++ "#version 120\n"
++ "#extension GL_OES_EGL_image_external : require\n";
++ QByteArray fsGLES = FS_GLES_PREAMBLE + FS;
++ QByteArray fsGL = FS_GL_PREAMBLE + FS;
++
++ QShaderDescription desc;
++ QShaderDescriptionPrivate *descData = QShaderDescriptionPrivate::get(&desc);
++
++ QShaderDescription::InOutVariable texCoordInput;
++ texCoordInput.name = "v_texcoord";
++ texCoordInput.type = QShaderDescription::Vec2;
++ texCoordInput.location = 0;
++
++ descData->inVars = { texCoordInput };
++
++ QShaderDescription::InOutVariable fragColorOutput;
++ fragColorOutput.name = "gl_FragColor";
++ fragColorOutput.type = QShaderDescription::Vec4;
++ fragColorOutput.location = 0;
++
++ descData->outVars = { fragColorOutput };
++
++ QShaderDescription::BlockVariable matrixBlockVar;
++ matrixBlockVar.name = "qt_Matrix";
++ matrixBlockVar.type = QShaderDescription::Mat4;
++ matrixBlockVar.offset = 0;
++ matrixBlockVar.size = 64;
++
++ QShaderDescription::BlockVariable opacityBlockVar;
++ opacityBlockVar.name = "qt_Opacity";
++ opacityBlockVar.type = QShaderDescription::Float;
++ opacityBlockVar.offset = 64;
++ opacityBlockVar.size = 4;
++
++ QShaderDescription::UniformBlock ubufStruct;
++ ubufStruct.blockName = "buf";
++ ubufStruct.structName = "ubuf";
++ ubufStruct.size = 64 + 4;
++ ubufStruct.binding = 0;
++ ubufStruct.members = { matrixBlockVar, opacityBlockVar };
++
++ descData->uniformBlocks = { ubufStruct };
++
++ QShaderDescription::InOutVariable samplerTex0;
++ samplerTex0.name = "tex0";
++ samplerTex0.type = QShaderDescription::SamplerExternalOES;
++ samplerTex0.binding = 1;
++
++ descData->combinedImageSamplers = { samplerTex0 };
++
++ QShader shaderPack;
++ shaderPack.setStage(QShader::FragmentStage);
++ shaderPack.setDescription(desc);
++ shaderPack.setShader(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)), QShaderCode(fsGLES));
++ shaderPack.setShader(QShaderKey(QShader::GlslShader, QShaderVersion(120)), QShaderCode(fsGL));
++
++ setShader(FragmentStage, shaderPack);
++ #else
++ Q_UNUSED(shaderFilename);
++ #endif
++ }
++
++ bool QWaylandBufferMaterialShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *)
++ {
++ bool changed = false;
++ QByteArray *buf = state.uniformData();
++ Q_ASSERT(buf->size() >= 68);
++
++ if (state.isMatrixDirty()) {
++ const QMatrix4x4 m = state.combinedMatrix();
++ memcpy(buf->data(), m.constData(), 64);
++ changed = true;
++ }
++
++ if (state.isOpacityDirty()) {
++ const float opacity = state.opacity();
++ memcpy(buf->data() + 64, &opacity, 4);
++ changed = true;
++ }
++
++ return changed;
++ }
++
++ void QWaylandBufferMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
++ QSGMaterial *newMaterial, QSGMaterial *)
++ {
++ Q_UNUSED(state);
++
++ QWaylandBufferMaterial *material = static_cast<QWaylandBufferMaterial *>(newMaterial);
++ switch (binding) {
++ case 1:
++ *texture = material->m_scenegraphTextures.at(0);
++ break;
++ case 2:
++ *texture = material->m_scenegraphTextures.at(1);
++ break;
++ case 3:
++ *texture = material->m_scenegraphTextures.at(2);
++ break;
++ default:
++ return;
++ }
++
++ // This is for the shared memory case, and is a no-op for others,
++ // this is where the upload from the QImage happens when not yet done.
++ // ### or is this too late? (if buffer.image() disappears in the meantime then this is the wrong...)
++ if (*texture)
++ (*texture)->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
++ }
++
++ QWaylandBufferMaterial::QWaylandBufferMaterial(QWaylandBufferRef::BufferFormatEgl format)
++ : m_format(format)
++ {
++ setFlag(bufferTypes[m_format].materialFlags);
++ }
++
++ QWaylandBufferMaterial::~QWaylandBufferMaterial()
++ {
++ qDeleteAll(m_scenegraphTextures);
++ }
++
++ void QWaylandBufferMaterial::setTextureForPlane(int plane,
++ QOpenGLTexture *texture,
++ QSGTexture *scenegraphTexture)
++ {
++ if (plane < 0 || plane >= bufferTypes[m_format].planeCount) {
++ qWarning("plane index is out of range");
++ return;
++ }
++
++ texture->bind();
++ setTextureParameters(texture->target());
++
++ ensureTextures(plane - 1);
++
++ if (m_textures.size() <= plane) {
++ m_textures << texture;
++ m_scenegraphTextures << scenegraphTexture;
++ } else {
++ delete m_scenegraphTextures[plane];
++
++ m_textures[plane] = texture;
++ m_scenegraphTextures[plane] = scenegraphTexture;
++ }
++ }
++
++ void QWaylandBufferMaterial::bind()
++ {
++ ensureTextures(bufferTypes[m_format].planeCount);
++
++ switch (m_textures.size()) {
++ case 3:
++ if (m_textures[2])
++ m_textures[2]->bind(2);
++ Q_FALLTHROUGH();
++ case 2:
++ if (m_textures[1])
++ m_textures[1]->bind(1);
++ Q_FALLTHROUGH();
++ case 1:
++ if (m_textures[0])
++ m_textures[0]->bind(0);
++ }
++ }
++
++ QSGMaterialType *QWaylandBufferMaterial::type() const
++ {
++ return const_cast<QSGMaterialType *>(&bufferTypes[m_format].materialType);
++ }
++
++ QSGMaterialShader *QWaylandBufferMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
++ {
++ Q_UNUSED(renderMode);
++ return new QWaylandBufferMaterialShader(m_format);
++ }
++
++
++ void QWaylandBufferMaterial::setTextureParameters(GLenum target)
++ {
++ QOpenGLFunctions *gl = QOpenGLContext::currentContext()->functions();
++ gl->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
++ gl->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
++ gl->glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
++ gl->glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
++ }
++
++ //TODO move this into a separate centralized texture management class
++ void QWaylandBufferMaterial::ensureTextures(int count)
++ {
++ for (int plane = m_textures.size(); plane < count; plane++) {
++ m_textures << nullptr;
++ m_scenegraphTextures << nullptr;
++ }
++ }
++
++ void QWaylandBufferMaterial::setBufferRef(QWaylandQuickItem *surfaceItem, const QWaylandBufferRef &ref)
++ {
++ m_bufferRef = ref;
++ for (int plane = 0; plane < bufferTypes[ref.bufferFormatEgl()].planeCount; plane++) {
++ if (auto texture = ref.toOpenGLTexture(plane)) {
++ QQuickWindow::CreateTextureOptions opt;
++ QWaylandQuickSurface *waylandSurface = qobject_cast<QWaylandQuickSurface *>(surfaceItem->surface());
++ if (waylandSurface != nullptr && waylandSurface->useTextureAlpha() && !waylandSurface->isOpaque())
++ opt |= QQuickWindow::TextureHasAlphaChannel;
++ QSGTexture *scenegraphTexture;
++ if (ref.bufferFormatEgl() == QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES) {
++ scenegraphTexture = QNativeInterface::QSGOpenGLTexture::fromNativeExternalOES(texture->textureId(),
++ surfaceItem->window(),
++ ref.size(),
++ opt);
++ } else {
++ scenegraphTexture = QNativeInterface::QSGOpenGLTexture::fromNative(texture->textureId(),
++ surfaceItem->window(),
++ ref.size(),
++ opt);
++ }
++ scenegraphTexture->setFiltering(surfaceItem->smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
++ setTextureForPlane(plane, texture, scenegraphTexture);
++ }
++ }
++
++ bind();
++ }
++ #endif // QT_CONFIG(opengl)
++
++ QMutex *QWaylandQuickItemPrivate::mutex = nullptr;
++
++ class QWaylandSurfaceTextureProvider : public QSGTextureProvider
++ {
++ public:
++ QWaylandSurfaceTextureProvider()
++ {
++ }
++
++ ~QWaylandSurfaceTextureProvider() override
++ {
++ // Note: If the ClientBuffer is deleted from the render thread, it happens on a deferred delete
++ // which is processed during the sync phase. Or it may be deleted from the main thread. In
++ // either case, we know the compositor is not shutting down simultaneously. Therefore, we can
++ // safely rely on the value of m_compositorDestroyed to stay the same for the duration of this
++ // destructor.
++ //
++ // Calling setDestroyed() on the buffer will cause it to skip its final sendRelease()
++ // on destruction, which would otherwise cause a crash when the compositor has been
++ // destroyed.
++ if (m_compositorDestroyed.loadAcquire() && m_ref.hasBuffer())
++ m_ref.buffer()->setDestroyed();
++
++ delete m_sgTex;
++ }
++
++ void setBufferRef(QWaylandQuickItem *surfaceItem, const QWaylandBufferRef &buffer)
++ {
++ Q_ASSERT(QThread::currentThread() == thread());
++ m_ref = buffer;
++ delete m_sgTex;
++ m_sgTex = nullptr;
++ if (m_ref.hasBuffer()) {
++ if (buffer.isSharedMemory()) {
++ m_sgTex = surfaceItem->window()->createTextureFromImage(buffer.image());
++ } else {
++ #if QT_CONFIG(opengl)
++ QQuickWindow::CreateTextureOptions opt;
++ QWaylandQuickSurface *surface = qobject_cast<QWaylandQuickSurface *>(surfaceItem->surface());
++ if (surface && surface->useTextureAlpha() && !surface->isOpaque()) {
++ opt |= QQuickWindow::TextureHasAlphaChannel;
++ }
++
++ auto texture = buffer.toOpenGLTexture();
++ GLuint textureId = texture->textureId();
++ auto size = surface->bufferSize();
++ m_sgTex = QNativeInterface::QSGOpenGLTexture::fromNative(textureId, surfaceItem->window(), size, opt);
++ #else
++ qCWarning(qLcWaylandCompositor) << "Without OpenGL support only shared memory textures are supported";
++ #endif
++ }
++ }
++ emit textureChanged();
++ }
++
++ QSGTexture *texture() const override
++ {
++ if (m_sgTex)
++ m_sgTex->setFiltering(m_smooth ? QSGTexture::Linear : QSGTexture::Nearest);
++ return m_sgTex;
++ }
++
++ void setSmooth(bool smooth) { m_smooth = smooth; }
++
++ void reportCompositorDestroyed()
++ {
++ m_compositorDestroyed.storeRelease(1);
++ deleteLater();
++ }
++
++ private:
++ bool m_smooth = false;
++ QSGTexture *m_sgTex = nullptr;
++
++ QAtomicInt m_compositorDestroyed;
++ QWaylandBufferRef m_ref;
++ };
++
++ void QWaylandQuickItemPrivate::handleDragUpdate(QWaylandSeat *seat, const QPointF &globalPosition)
++ {
++ #if QT_CONFIG(draganddrop)
++ Q_Q(QWaylandQuickItem);
++ QWaylandQuickOutput *currentOutput = qobject_cast<QWaylandQuickOutput *>(q->view()->output());
++ //TODO: also check if dragging onto other outputs
++ QWaylandQuickItem *targetItem = qobject_cast<QWaylandQuickItem *>(currentOutput->pickClickableItem(q->mapToScene(globalPosition)));
++ QWaylandSurface *targetSurface = targetItem ? targetItem->surface() : nullptr;
++ if (targetSurface) {
++ QPointF position = q->mapToItem(targetItem, globalPosition);
++ QPointF surfacePosition = targetItem->mapToSurface(position);
++ seat->drag()->dragMove(targetSurface, surfacePosition);
++ }
++ #else
++ Q_UNUSED(seat);
++ Q_UNUSED(globalPosition);
++ #endif // QT_CONFIG(draganddrop)
++ }
++
++ void QWaylandQuickItemPrivate::handleDragEnded(QWaylandSeat *seat)
++ {
++ #if QT_CONFIG(draganddrop)
++ isDragging = false;
++ seat->drag()->drop();
++ #else
++ Q_UNUSED(seat);
++ #endif // QT_CONFIG(draganddrop)
++ }
++
++ /*!
++ * \qmltype WaylandQuickItem
++ * \nativetype QWaylandQuickItem
++ * \inqmlmodule QtWayland.Compositor
++ * \since 5.8
++ * \brief The WaylandQuickItem type provides a Qt Quick item which represents the surface of a Wayland client.
++ *
++ * Qt Quick-based Wayland compositors can use this type to display a client's
++ * contents on an output device. It passes user input to the
++ * client.
++ */
++
++ /*!
++ * \class QWaylandQuickItem
++ * \inmodule QtWaylandCompositor
++ * \since 5.8
++ * \brief The QWaylandQuickItem class provides a Qt Quick item which represents the surface of a Wayland client.
++ *
++ * When writing a QWaylandCompositor in Qt Quick, this class can be used to display a
++ * client's contents on an output device and will pass user input to the
++ * client.
++ */
++
++ /*!
++ * Constructs a QWaylandQuickItem with the given \a parent.
++ */
++ QWaylandQuickItem::QWaylandQuickItem(QQuickItem *parent)
++ : QWaylandQuickItem(*new QWaylandQuickItemPrivate(), parent)
++ {
++ }
++
++ /*!
++ * \internal
++ */
++ QWaylandQuickItem::QWaylandQuickItem(QWaylandQuickItemPrivate &dd, QQuickItem *parent)
++ : QQuickItem(dd, parent)
++ {
++ d_func()->init();
++ connect(this, &QQuickItem::activeFocusChanged, this, &QWaylandQuickItem::updateFocus);
++ }
++
++ /*!
++ * Destroy the QWaylandQuickItem.
++ */
++ QWaylandQuickItem::~QWaylandQuickItem()
++ {
++ Q_D(QWaylandQuickItem);
++ disconnect(this, &QQuickItem::windowChanged, this, &QWaylandQuickItem::updateWindow);
++ disconnect(this, &QQuickItem::activeFocusChanged, this, &QWaylandQuickItem::updateFocus);
++ QMutexLocker locker(d->mutex);
++ if (d->provider) {
++ disconnect(d->texProviderConnection);
++ d->provider->deleteLater();
++ }
++ }
++
++ /*!
++ * \qmlproperty WaylandCompositor QtWayland.Compositor::WaylandQuickItem::compositor
++ *
++ * This property holds the compositor for the surface rendered by this WaylandQuickItem.
++ */
++
++ /*!
++ * \property QWaylandQuickItem::compositor
++ *
++ * This property holds the compositor for the surface rendered by this QWaylandQuickItem.
++ */
++ QWaylandCompositor *QWaylandQuickItem::compositor() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->view->surface() ? d->view->surface()->compositor() : nullptr;
++ }
++
++ /*!
++ * Returns the view rendered by this QWaylandQuickItem.
++ */
++ QWaylandView *QWaylandQuickItem::view() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->view.data();
++ }
++
++ /*!
++ * \qmlproperty WaylandSurface QtWayland.Compositor::WaylandQuickItem::surface
++ *
++ * This property holds the surface rendered by this WaylandQuickItem.
++ */
++
++ /*!
++ * \property QWaylandQuickItem::surface
++ *
++ * This property holds the surface rendered by this QWaylandQuickItem.
++ */
++
++ QWaylandSurface *QWaylandQuickItem::surface() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->view->surface();
++ }
++
++ void QWaylandQuickItem::setSurface(QWaylandSurface *surface)
++ {
++ Q_D(QWaylandQuickItem);
++ QWaylandSurface *oldSurf = d->view->surface();
++ if (oldSurf == surface)
++ return;
++
++ QWaylandCompositor *oldComp = oldSurf ? oldSurf->compositor() : nullptr;
++ QWaylandCompositor *newComp = surface ? surface->compositor() : nullptr;
++ if (oldComp != newComp)
++ emit compositorChanged();
++
++ d->view->setSurface(surface);
++
++ updateFocus();
++ update();
++ }
++
++ /*!
++ * \qmlproperty enum QtWayland.Compositor::WaylandQuickItem::origin
++ *
++ * This property reflects the origin of the surface's current buffer, that is,
++ * which corner of the buffer corresponds to coordinate (0, 0).
++ *
++ * \sa WaylandSurface::origin
++ */
++
++ /*!
++ * \property QWaylandQuickItem::origin
++ *
++ * This property reflects the origin of the surface's current buffer, that is,
++ * which corner of the buffer corresponds to coordinate (0, 0).
++ *
++ * \sa QWaylandSurface::origin
++ */
++ QWaylandSurface::Origin QWaylandQuickItem::origin() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->origin;
++ }
++
++ bool QWaylandQuickItem::isTextureProvider() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return QQuickItem::isTextureProvider() || d->provider;
++ }
++
++ /*!
++ * Returns the texture provider of this QWaylandQuickItem.
++ */
++ QSGTextureProvider *QWaylandQuickItem::textureProvider() const
++ {
++ Q_D(const QWaylandQuickItem);
++
++ if (QQuickItem::isTextureProvider())
++ return QQuickItem::textureProvider();
++
++ return d->provider;
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::mousePressEvent(QMouseEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (!d->shouldSendInputEvents()) {
++ event->ignore();
++ return;
++ }
++
++ if (!inputRegionContains(event->position())) {
++ event->ignore();
++ return;
++ }
++
++ QWaylandSeat *seat = compositor()->seatFor(event);
++
++ if (d->focusOnClick)
++ takeFocus(seat);
++
++ seat->sendMouseMoveEvent(d->view.data(), mapToSurface(event->position()), event->scenePosition());
++ seat->sendMousePressEvent(event->button());
++ d->hoverPos = event->position();
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::mouseMoveEvent(QMouseEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ if (d->isDragging) {
++ d->handleDragUpdate(seat, event->position());
++ } else {
++ seat->sendMouseMoveEvent(d->view.data(), mapToSurface(event->position()), event->scenePosition());
++ d->hoverPos = event->position();
++ }
++ } else {
++ emit mouseMove(event->scenePosition());
++ event->ignore();
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::mouseReleaseEvent(QMouseEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ if (d->isDragging)
++ d->handleDragEnded(seat);
++ else
++ seat->sendMouseReleaseEvent(event->button());
++ } else {
++ emit mouseRelease();
++ event->ignore();
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::hoverEnterEvent(QHoverEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (!inputRegionContains(event->position())) {
++ event->ignore();
++ return;
++ }
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ seat->sendMouseMoveEvent(d->view.data(), event->position(), mapToScene(event->position()));
++ d->hoverPos = event->position();
++ } else {
++ event->ignore();
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::hoverMoveEvent(QHoverEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (surface()) {
++ if (!inputRegionContains(event->position())) {
++ event->ignore();
++ return;
++ }
++ }
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ if (event->position() != d->hoverPos) {
++ seat->sendMouseMoveEvent(d->view.data(), mapToSurface(event->position()), mapToScene(event->position()));
++ d->hoverPos = event->position();
++ }
++ } else {
++ event->ignore();
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::hoverLeaveEvent(QHoverEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ if (d->view.data() == seat->mouseFocus()) {
++ seat->setMouseFocus(nullptr);
++ return;
++ }
++ }
++
++ event->ignore();
++ }
++
++ #if QT_CONFIG(wheelevent)
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::wheelEvent(QWheelEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ if (!inputRegionContains(event->position())) {
++ event->ignore();
++ return;
++ }
++
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ // TODO: fix this to send a single event, when diagonal scrolling is supported
++ if (event->angleDelta().x() != 0)
++ seat->sendMouseWheelEvent(Qt::Horizontal, event->angleDelta().x());
++ if (event->angleDelta().y() != 0)
++ seat->sendMouseWheelEvent(Qt::Vertical, event->angleDelta().y());
++ } else {
++ event->ignore();
++ }
++ }
++ #endif
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::keyPressEvent(QKeyEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ if (seat->setKeyboardFocus(d->view->surface()))
++ seat->sendFullKeyEvent(event);
++ else
++ qWarning() << "Unable to set keyboard focus, cannot send key press event";
++ } else {
++ event->ignore();
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::keyReleaseEvent(QKeyEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++ seat->sendFullKeyEvent(event);
++ } else {
++ event->ignore();
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::touchEvent(QTouchEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents() && d->touchEventsEnabled) {
++ QWaylandSeat *seat = compositor()->seatFor(event);
++
++ QPointF pointPos;
++ const QList<QTouchEvent::TouchPoint> &points = event->points();
++ if (!points.isEmpty())
++ pointPos = points.at(0).position();
++
++ if (event->type() == QEvent::TouchBegin && !inputRegionContains(pointPos)) {
++ event->ignore();
++ return;
++ }
++
++ if (event->type() == QEvent::TouchUpdate && d->isDragging)
++ d->handleDragUpdate(seat, pointPos);
++
++ event->accept();
++ if (seat->mouseFocus() != d->view.data()) {
++ seat->sendMouseMoveEvent(d->view.data(), pointPos, mapToScene(pointPos));
++ }
++ seat->sendFullTouchEvent(surface(), event);
++
++ if (event->type() == QEvent::TouchBegin) {
++ d->touchingSeats.append(seat);
++ } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
++ if (d->isDragging)
++ d->handleDragEnded(seat);
++ d->touchingSeats.removeOne(seat);
++ }
++
++ if (event->type() == QEvent::TouchBegin && d->focusOnClick)
++ takeFocus(seat);
++ } else {
++ event->ignore();
++ }
++ }
++
++ void QWaylandQuickItem::touchUngrabEvent()
++ {
++ Q_D(QWaylandQuickItem);
++
++ if (d->shouldSendInputEvents()) {
++ for (auto seat : std::as_const(d->touchingSeats))
++ seat->sendTouchCancelEvent(surface()->client());
++ }
++
++ d->touchingSeats.clear();
++ }
++
++ #if QT_CONFIG(im)
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::inputMethodEvent(QInputMethodEvent *event)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->shouldSendInputEvents()) {
++ d->oldSurface->inputMethodControl()->inputMethodEvent(event);
++ } else {
++ event->ignore();
++ }
++ }
++ #endif
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::surfaceChangedEvent(QWaylandSurface *newSurface, QWaylandSurface *oldSurface)
++ {
++ Q_UNUSED(newSurface);
++ Q_UNUSED(oldSurface);
++ }
++
++ void QWaylandQuickItem::handleSubsurfaceAdded(QWaylandSurface *childSurface)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->subsurfaceHandler.isNull()) {
++ QWaylandQuickItem *childItem = new QWaylandQuickItem;
++ childItem->setSurface(childSurface);
++ childItem->setVisible(true);
++ childItem->setParentItem(this);
++ childItem->setParent(this);
++ childItem->setPosition(QWaylandSurfacePrivate::get(childSurface)->subsurfacePosition() * d->scaleFactor());
++ connect(childSurface, &QWaylandSurface::subsurfacePositionChanged, childItem, &QWaylandQuickItem::handleSubsurfacePosition);
++ connect(childSurface, &QWaylandSurface::destroyed, childItem, &QObject::deleteLater);
++ } else {
++ bool success = QMetaObject::invokeMethod(d->subsurfaceHandler, "handleSubsurfaceAdded", Q_ARG(QWaylandSurface *, childSurface));
++ if (!success)
++ success = QMetaObject::invokeMethod(d->subsurfaceHandler, "handleSubsurfaceAdded",
++ Q_ARG(QVariant, QVariant::fromValue(childSurface)));
++
++ if (!success)
++ qWarning("QWaylandQuickItem: subsurfaceHandler does not implement handleSubsurfaceAdded()");
++ }
++ }
++
++ void QWaylandQuickItem::handlePlaceAbove(QWaylandSurface *referenceSurface)
++ {
++ Q_D(QWaylandQuickItem);
++ auto *parent = qobject_cast<QWaylandQuickItem*>(parentItem());
++ if (!parent)
++ return;
++
++ if (parent->surface() == referenceSurface) {
++ d->placeAboveParent();
++ } else if (auto *sibling = d->findSibling(referenceSurface)) {
++ d->placeAboveSibling(sibling);
++ } else {
++ qWarning() << "Couldn't find QWaylandQuickItem for surface" << referenceSurface
++ << "when handling wl_subsurface.place_above";
++ }
++ }
++
++ void QWaylandQuickItem::handlePlaceBelow(QWaylandSurface *referenceSurface)
++ {
++ Q_D(QWaylandQuickItem);
++ QWaylandQuickItem *parent = qobject_cast<QWaylandQuickItem*>(parentItem());
++ if (!parent)
++ return;
++
++ if (parent->surface() == referenceSurface) {
++ d->placeBelowParent();
++ } else if (auto *sibling = d->findSibling(referenceSurface)) {
++ d->placeBelowSibling(sibling);
++ } else {
++ qWarning() << "Couldn't find QWaylandQuickItem for surface" << referenceSurface
++ << "when handling wl_subsurface.place_below";
++ }
++ }
++
++ void QWaylandQuickItem::updateFocus()
++ {
++ Q_D(const QWaylandQuickItem);
++ if (hasActiveFocus() && compositor())
++ compositor()->defaultSeat()->setKeyboardFocus(d->view->surface());
++ }
++
++ /*!
++ \qmlproperty object QtWayland.Compositor::WaylandQuickItem::subsurfaceHandler
++
++ This property provides a way to override the default subsurface behavior.
++
++ By default, Qt will create a new SurfaceItem as a child of this item, and maintain the correct position.
++
++ To override the default, assign a handler object to this property. The handler should implement
++ a handleSubsurfaceAdded(WaylandSurface) function.
++
++ \code
++ ShellSurfaceItem {
++ subsurfaceHandler: QtObject {
++ function handleSubsurfaceAdded(child) {
++ // create custom surface item, and connect the subsurfacePositionChanged signal
++ }
++ }
++ }
++ \endcode
++
++ The default value of this property is \c null.
++ */
++
++ /*!
++ * \property QWaylandQuickItem::subsurfaceHandler
++ *
++ * This property provides a way to override the default subsurface behavior.
++ *
++ * By default, Qt will create a new QWaylandQuickItem as a child of this item
++ * and maintain the correct position.
++ *
++ * To override the default behavior, assign a handler object to this property.
++ * The handler must implement a \c handleSubsurfaceAdded(QWaylandSurface*)
++ * method.
++ *
++ * The default value of this property is \nullptr.
++ */
++ QObject *QWaylandQuickItem::subsurfaceHandler() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->subsurfaceHandler.data();
++ }
++
++ void QWaylandQuickItem::setSubsurfaceHandler(QObject *handler)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->subsurfaceHandler.data() != handler) {
++ d->subsurfaceHandler = handler;
++ emit subsurfaceHandlerChanged();
++ }
++ }
++
++ /*!
++ * \qmlproperty WaylandOutput QtWayland.Compositor::WaylandQuickItem::output
++ *
++ * This property holds the output on which this item is displayed.
++ */
++ /*!
++ * \property QWaylandQuickItem::output
++ *
++ * This property holds the output on which this item is displayed.
++ */
++ QWaylandOutput *QWaylandQuickItem::output() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->view->output();
++ }
++
++ void QWaylandQuickItem::setOutput(QWaylandOutput *output)
++ {
++ Q_D(QWaylandQuickItem);
++ d->view->setOutput(output);
++ }
++
++ /*!
++ * \qmlproperty bool QtWayland.Compositor::WaylandQuickItem::bufferLocked
++ *
++ * This property holds whether the item's buffer is currently locked. As long as
++ * the buffer is locked, it will not be released and returned to the client.
++ *
++ * The default is false.
++ */
++ /*!
++ * \property QWaylandQuickItem::bufferLocked
++ *
++ * This property holds whether the item's buffer is currently locked. As long as
++ * the buffer is locked, it will not be released and returned to the client.
++ *
++ * The default is false.
++ */
++ bool QWaylandQuickItem::isBufferLocked() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->view->isBufferLocked();
++ }
++
++ void QWaylandQuickItem::setBufferLocked(bool locked)
++ {
++ Q_D(QWaylandQuickItem);
++ d->view->setBufferLocked(locked);
++ }
++
++ /*!
++ * \property QWaylandQuickItem::allowDiscardFrontBuffer
++ *
++ * By default, the item locks the current buffer until a new buffer is available
++ * and updatePaintNode() is called. Set this property to true to allow Qt to release the buffer
++ * immediately when the throttling view is no longer using it. This is useful for items that have
++ * slow update intervals.
++ */
++ bool QWaylandQuickItem::allowDiscardFrontBuffer() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->view->allowDiscardFrontBuffer();
++ }
++
++ void QWaylandQuickItem::setAllowDiscardFrontBuffer(bool discard)
++ {
++ Q_D(QWaylandQuickItem);
++ d->view->setAllowDiscardFrontBuffer(discard);
++ }
++
++ /*!
++ * \qmlmethod void WaylandQuickItem::setPrimary()
++ *
++ * Makes this WaylandQuickItem the primary view for the surface.
++ */
++
++ /*!
++ * Makes this QWaylandQuickItem's view the primary view for the surface.
++ *
++ * \sa QWaylandSurface::primaryView
++ */
++ void QWaylandQuickItem::setPrimary()
++ {
++ Q_D(QWaylandQuickItem);
++ d->view->setPrimary();
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::handleSurfaceChanged()
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->oldSurface) {
++ disconnect(d->oldSurface.data(), &QWaylandSurface::parentChanged, this, &QWaylandQuickItem::parentChanged);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::destinationSizeChanged, this, &QWaylandQuickItem::updateSize);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::bufferScaleChanged, this, &QWaylandQuickItem::updateSize);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::configure, this, &QWaylandQuickItem::updateBuffer);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::redraw, this, &QQuickItem::update);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::childAdded, this, &QWaylandQuickItem::handleSubsurfaceAdded);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::subsurfacePlaceAbove, this, &QWaylandQuickItem::handlePlaceAbove);
++ disconnect(d->oldSurface.data(), &QWaylandSurface::subsurfacePlaceBelow, this, &QWaylandQuickItem::handlePlaceBelow);
++ #if QT_CONFIG(draganddrop)
++ disconnect(d->oldSurface.data(), &QWaylandSurface::dragStarted, this, &QWaylandQuickItem::handleDragStarted);
++ #endif
++ #if QT_CONFIG(im)
++ disconnect(d->oldSurface->inputMethodControl(), &QWaylandInputMethodControl::updateInputMethod, this, &QWaylandQuickItem::updateInputMethod);
++ #endif
++ }
++ if (QWaylandSurface *newSurface = d->view->surface()) {
++ connect(newSurface, &QWaylandSurface::parentChanged, this, &QWaylandQuickItem::parentChanged);
++ connect(newSurface, &QWaylandSurface::destinationSizeChanged, this, &QWaylandQuickItem::updateSize);
++ connect(newSurface, &QWaylandSurface::bufferScaleChanged, this, &QWaylandQuickItem::updateSize);
++ connect(newSurface, &QWaylandSurface::configure, this, &QWaylandQuickItem::updateBuffer);
++ connect(newSurface, &QWaylandSurface::redraw, this, &QQuickItem::update);
++ connect(newSurface, &QWaylandSurface::childAdded, this, &QWaylandQuickItem::handleSubsurfaceAdded);
++ connect(newSurface, &QWaylandSurface::subsurfacePlaceAbove, this, &QWaylandQuickItem::handlePlaceAbove);
++ connect(newSurface, &QWaylandSurface::subsurfacePlaceBelow, this, &QWaylandQuickItem::handlePlaceBelow);
++ #if QT_CONFIG(draganddrop)
++ connect(newSurface, &QWaylandSurface::dragStarted, this, &QWaylandQuickItem::handleDragStarted);
++ #endif
++ #if QT_CONFIG(im)
++ connect(newSurface->inputMethodControl(), &QWaylandInputMethodControl::updateInputMethod, this, &QWaylandQuickItem::updateInputMethod);
++ #endif
++
++ if (newSurface->origin() != d->origin) {
++ d->origin = newSurface->origin();
++ emit originChanged();
++ }
++ if (window()) {
++ QWaylandOutput *output = newSurface->compositor()->outputFor(window());
++ d->view->setOutput(output);
++ }
++ for (const auto &subsurface : std::as_const(QWaylandSurfacePrivate::get(newSurface)->subsurfaceChildren)) {
++ if (!subsurface.isNull())
++ handleSubsurfaceAdded(subsurface.data());
++ }
++
++ updateSize();
++ }
++ surfaceChangedEvent(d->view->surface(), d->oldSurface);
++ d->oldSurface = d->view->surface();
++ #if QT_CONFIG(im)
++ updateInputMethod(Qt::ImQueryInput);
++ #endif
++ }
++
++ /*!
++ * Calling this function causes the item to take the focus of the
++ * input \a device.
++ */
++ void QWaylandQuickItem::takeFocus(QWaylandSeat *device)
++ {
++ forceActiveFocus();
++
++ if (!surface() || !surface()->client())
++ return;
++
++ QWaylandSeat *target = device;
++ if (!target) {
++ target = compositor()->defaultSeat();
++ }
++ target->setKeyboardFocus(surface());
++
++ qCDebug(qLcWaylandCompositorInputMethods) << Q_FUNC_INFO << " surface:" << surface()
++ << ", client:" << surface()->client()
++ << ", textinputprotocol:" << (int)(surface()->client()->textInputProtocols());
++ if (surface()->client()->textInputProtocols().testFlag(QWaylandClient::TextInputProtocol::TextInputV2)) {
++ QWaylandTextInput *textInput = QWaylandTextInput::findIn(target);
++ if (textInput)
++ textInput->setFocus(surface());
++ }
++
++ if (surface()->client()->textInputProtocols().testFlag(QWaylandClient::TextInputProtocol::TextInputV3)) {
++ QWaylandTextInputV3 *textInputV3 = QWaylandTextInputV3::findIn(target);
++ if (textInputV3)
++ textInputV3->setFocus(surface());
++ }
++
++ if (surface()->client()->textInputProtocols().testFlag(QWaylandClient::TextInputProtocol::QtTextInputMethodV1)) {
++ QWaylandQtTextInputMethod *textInputMethod = QWaylandQtTextInputMethod::findIn(target);
++ if (textInputMethod)
++ textInputMethod->setFocus(surface());
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::handleBufferLockedChanged()
++ {
++ Q_D(QWaylandQuickItem);
++
++ // Apply the latest surface size
++ if (!d->view->isBufferLocked()) {
++ updateSize();
++ update();
++ }
++ }
++
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ void QWaylandQuickItem::surfaceMappedChanged()
++ {
++ update();
++ }
++ #endif
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::parentChanged(QWaylandSurface *newParent, QWaylandSurface *oldParent)
++ {
++ Q_UNUSED(oldParent);
++
++ if (newParent) {
++ setPaintEnabled(true);
++ setVisible(true);
++ setOpacity(1);
++ setEnabled(true);
++ }
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandQuickItem::updateSize()
++ {
++ Q_D(QWaylandQuickItem);
++
++ // No resize if buffer is locked
++ if (isBufferLocked()) {
++ qWarning() << "No update on item size as the buffer is currently locked";
++ return;
++ }
++
++ QSize size(0, 0);
++ if (surface())
++ size = surface()->destinationSize() * d->scaleFactor();
++
++ setImplicitSize(size.width(), size.height());
++ }
++
++ /*!
++ * \qmlproperty bool QtWayland.Compositor::WaylandQuickItem::focusOnClick
++ *
++ * This property specifies whether the WaylandQuickItem should take focus when
++ * it is clicked or touched.
++ *
++ * The default is \c true.
++ */
++
++ /*!
++ * \property QWaylandQuickItem::focusOnClick
++ *
++ * This property specifies whether the QWaylandQuickItem should take focus when
++ * it is clicked or touched.
++ *
++ * The default is \c true.
++ */
++ bool QWaylandQuickItem::focusOnClick() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->focusOnClick;
++ }
++
++ void QWaylandQuickItem::setFocusOnClick(bool focus)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->focusOnClick == focus)
++ return;
++
++ d->focusOnClick = focus;
++ emit focusOnClickChanged();
++ }
++
++ /*!
++ * Returns \c true if the input region of this item's surface contains the
++ * position given by \a localPosition.
++ */
++ bool QWaylandQuickItem::inputRegionContains(const QPointF &localPosition) const
++ {
++ if (QWaylandSurface *s = surface())
++ return s->inputRegionContains(mapToSurface(localPosition));
++ return false;
++ }
++
++ /*!
++ * \qmlmethod point WaylandQuickItem::mapToSurface(point point)
++ *
++ * Maps the given \a point in this item's coordinate system to the equivalent
++ * point within the Wayland surface's coordinate system, and returns the mapped
++ * coordinate.
++ */
++
++ /*!
++ * Maps the given \a point in this item's coordinate system to the equivalent
++ * point within the Wayland surface's coordinate system, and returns the mapped
++ * coordinate.
++ */
++ QPointF QWaylandQuickItem::mapToSurface(const QPointF &point) const
++ {
++ Q_D(const QWaylandQuickItem);
++ if (!surface() || surface()->destinationSize().isEmpty())
++ return point / d->scaleFactor();
++
++ qreal xScale = width() / surface()->destinationSize().width();
++ qreal yScale = height() / surface()->destinationSize().height();
++
++ return QPointF(point.x() / xScale, point.y() / yScale);
++ }
++
++ /*!
++ * \qmlmethod point WaylandQuickItem::mapFromSurface(point point)
++ * \since 5.13
++ *
++ * Maps the given \a point in the Wayland surfaces's coordinate system to the equivalent
++ * point within this item's coordinate system, and returns the mapped coordinate.
++ */
++
++ /*!
++ * Maps the given \a point in the Wayland surfaces's coordinate system to the equivalent
++ * point within this item's coordinate system, and returns the mapped coordinate.
++ *
++ * \since 5.13
++ */
++ QPointF QWaylandQuickItem::mapFromSurface(const QPointF &point) const
++ {
++ Q_D(const QWaylandQuickItem);
++ if (!surface() || surface()->destinationSize().isEmpty())
++ return point * d->scaleFactor();
++
++ qreal xScale = width() / surface()->destinationSize().width();
++ qreal yScale = height() / surface()->destinationSize().height();
++
++ return QPointF(point.x() * xScale, point.y() * yScale);
++ }
++
++ #if QT_CONFIG(im)
++ QVariant QWaylandQuickItem::inputMethodQuery(Qt::InputMethodQuery query) const
++ {
++ return inputMethodQuery(query, QVariant());
++ }
++
++ QVariant QWaylandQuickItem::inputMethodQuery(Qt::InputMethodQuery query, QVariant argument) const
++ {
++ Q_D(const QWaylandQuickItem);
++
++ if (query == Qt::ImEnabled)
++ return QVariant((flags() & ItemAcceptsInputMethod) != 0);
++
++ if (d->oldSurface)
++ return d->oldSurface->inputMethodControl()->inputMethodQuery(query, argument);
++
++ return QVariant();
++ }
++ #endif
++
++ /*!
++ \qmlproperty bool QtWayland.Compositor::WaylandQuickItem::paintEnabled
++ \default true
++
++ Setting this property to \c false will stop rendering the item. As opposed
++ to hiding the item by setting \l{Item::visible}{visible} to \c false, it
++ will not prevent mouse or keyboard input from reaching the item.
++ */
++
++ /*!
++ \property QWaylandQuickItem::paintEnabled
++
++ Setting this property to \c false will stop rendering the item. As opposed
++ to hiding the item by setting \l{Item::visible}{visible} to \c false, it
++ will not prevent mouse or keyboard input from reaching the item.
++ */
++ bool QWaylandQuickItem::isPaintEnabled() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->paintEnabled;
++ }
++
++ void QWaylandQuickItem::setPaintEnabled(bool enabled)
++ {
++ Q_D(QWaylandQuickItem);
++
++ if (enabled != d->paintEnabled) {
++ d->paintEnabled = enabled;
++ emit paintEnabledChanged();
++ }
++
++ update();
++ }
++
++ /*!
++ \qmlproperty bool QtWayland.Compositor::WaylandQuickItem::touchEventsEnabled
++
++ This property holds \c true if touch events are forwarded to the client
++ surface, \c false otherwise.
++ */
++
++ /*!
++ \property QWaylandQuickItem::touchEventsEnabled
++
++ This property holds \c true if touch events are forwarded to the client
++ surface, \c false otherwise.
++ */
++ bool QWaylandQuickItem::touchEventsEnabled() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->touchEventsEnabled;
++ }
++
++ void QWaylandQuickItem::updateBuffer(bool hasBuffer)
++ {
++ Q_D(QWaylandQuickItem);
++ Q_UNUSED(hasBuffer);
++ if (d->origin != surface()->origin()) {
++ d->origin = surface()->origin();
++ emit originChanged();
++ }
++ }
++
++ void QWaylandQuickItem::updateWindow()
++ {
++ Q_D(QWaylandQuickItem);
++
++ QQuickWindow *newWindow = window();
++ if (newWindow == d->connectedWindow)
++ return;
++
++ if (d->connectedWindow) {
++ disconnect(d->connectedWindow, &QQuickWindow::beforeSynchronizing, this, &QWaylandQuickItem::beforeSync);
++ disconnect(d->connectedWindow, &QQuickWindow::screenChanged, this, &QWaylandQuickItem::updateSize);
++ }
++
++ d->connectedWindow = newWindow;
++
++ if (d->connectedWindow) {
++ connect(d->connectedWindow, &QQuickWindow::beforeSynchronizing, this, &QWaylandQuickItem::beforeSync, Qt::DirectConnection);
++ connect(d->connectedWindow, &QQuickWindow::screenChanged, this, &QWaylandQuickItem::updateSize); // new screen may have new dpr
++
++ if (compositor()) {
++ QWaylandOutput *output = compositor()->outputFor(d->connectedWindow);
++ Q_ASSERT(output);
++ d->view->setOutput(output);
++ }
++
++ updateSize(); // because scaleFactor depends on devicePixelRatio, which may be different for the new window
++ }
++ }
++
++ void QWaylandQuickItem::updateOutput()
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->view->output() == d->connectedOutput)
++ return;
++
++ if (d->connectedOutput)
++ disconnect(d->connectedOutput, &QWaylandOutput::scaleFactorChanged, this, &QWaylandQuickItem::updateSize);
++
++ d->connectedOutput = d->view->output();
++
++ if (d->connectedOutput)
++ connect(d->connectedOutput, &QWaylandOutput::scaleFactorChanged, this, &QWaylandQuickItem::updateSize);
++
++ updateSize();
++ }
++
++ void QWaylandQuickItem::beforeSync()
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->view->advance()) {
++ d->newTexture = true;
++ update();
++ }
++ }
++
++ #if QT_CONFIG(im)
++ void QWaylandQuickItem::updateInputMethod(Qt::InputMethodQueries queries)
++ {
++ Q_D(QWaylandQuickItem);
++
++ setFlag(QQuickItem::ItemAcceptsInputMethod,
++ d->oldSurface ? d->oldSurface->inputMethodControl()->enabled() : false);
++ QQuickItem::updateInputMethod(queries | Qt::ImEnabled);
++ }
++ #endif
++
++ /*!
++ * \qmlsignal void QtWayland.Compositor::WaylandQuickItem::surfaceDestroyed()
++ *
++ * This signal is emitted when the client has destroyed the \c wl_surface associated
++ * with the WaylandQuickItem. The handler for this signal is expected to either destroy the
++ * WaylandQuickItem immediately or start a close animation and then destroy the Item.
++ *
++ * If an animation is started, bufferLocked should be set to ensure the item keeps its content
++ * until the animation finishes
++ *
++ * \sa bufferLocked
++ */
++
++ /*!
++ * \fn void QWaylandQuickItem::surfaceDestroyed()
++ *
++ * This signal is emitted when the client has destroyed the \c wl_surface associated
++ * with the QWaylandQuickItem. The handler for this signal is expected to either destroy the
++ * QWaylandQuickItem immediately or start a close animation and then destroy the Item.
++ *
++ * If an animation is started, bufferLocked should be set to ensure the item keeps its content
++ * until the animation finishes
++ *
++ * \sa QWaylandQuickItem::bufferLocked
++ */
++
++ QSGNode *QWaylandQuickItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
++ {
++ Q_D(QWaylandQuickItem);
++ d->lastMatrix = data->transformNode->combinedMatrix();
++ const bool bufferHasContent = d->view->currentBuffer().hasContent();
++
++ if (d->view->isBufferLocked() && d->paintEnabled)
++ return oldNode;
++
++ if (!bufferHasContent || !d->paintEnabled || !surface()) {
++ delete oldNode;
++ return nullptr;
++ }
++
++ QWaylandBufferRef ref = d->view->currentBuffer();
++ const bool invertY = ref.origin() == QWaylandSurface::OriginBottomLeft;
++ const QRectF rect = invertY ? QRectF(0, height(), width(), -height())
++ : QRectF(0, 0, width(), height());
++
++ if (ref.isSharedMemory()
++ #if QT_CONFIG(opengl)
++ || bufferTypes[ref.bufferFormatEgl()].canProvideTexture
++ #endif
++ ) {
++ #if QT_CONFIG(opengl)
++ if (oldNode && !d->paintByProvider) {
++ // Need to re-create a node
++ delete oldNode;
++ oldNode = nullptr;
++ }
++ d->paintByProvider = true;
++ #endif
++ // This case could covered by the more general path below, but this is more efficient (especially when using ShaderEffect items).
++ QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
++
++ if (!node) {
++ node = new QSGSimpleTextureNode();
++ if (smooth())
++ node->setFiltering(QSGTexture::Linear);
++ d->newTexture = true;
++ }
++
++ if (!d->provider) {
++ d->provider = new QWaylandSurfaceTextureProvider();
++ if (auto *c = compositor()) {
++ d->texProviderConnection =
++ QObject::connect(c, &QObject::destroyed, this, [d = d] {
++ if (d->provider) {
++ d->provider->reportCompositorDestroyed();
++ d->provider = nullptr;
++ }
++ QObject::disconnect(d->texProviderConnection);
++ });
++ }
++ }
++
++ if (d->newTexture) {
++ d->newTexture = false;
++ d->provider->setBufferRef(this, ref);
++ node->setTexture(d->provider->texture());
++ }
++
++ d->provider->setSmooth(smooth());
++ node->setRect(rect);
++
++ qreal scale = surface()->bufferScale();
++ QRectF source = surface()->sourceGeometry();
++ node->setSourceRect(QRectF(source.topLeft() * scale, source.size() * scale));
++
++ return node;
++ }
++
++ #if QT_CONFIG(opengl)
++ Q_ASSERT(!d->provider);
++
++ if (oldNode && d->paintByProvider) {
++ // Need to re-create a node
++ delete oldNode;
++ oldNode = nullptr;
++ }
++ d->paintByProvider = false;
++
++ QSGGeometryNode *node = static_cast<QSGGeometryNode *>(oldNode);
++
++ if (!node) {
++ node = new QSGGeometryNode;
++ d->newTexture = true;
++ }
++
++ QSGGeometry *geometry = node->geometry();
++ QWaylandBufferMaterial *material = static_cast<QWaylandBufferMaterial *>(node->material());
++
++ if (!geometry)
++ geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
++
++ if (!material)
++ material = new QWaylandBufferMaterial(ref.bufferFormatEgl());
++
++ if (d->newTexture) {
++ d->newTexture = false;
++ material->setBufferRef(this, ref);
++ }
++
++ const QSize surfaceSize = ref.size() / surface()->bufferScale();
++ const QRectF sourceGeometry = surface()->sourceGeometry();
++ const QRectF normalizedCoordinates =
++ sourceGeometry.isValid()
++ ? QRectF(sourceGeometry.x() / surfaceSize.width(),
++ sourceGeometry.y() / surfaceSize.height(),
++ sourceGeometry.width() / surfaceSize.width(),
++ sourceGeometry.height() / surfaceSize.height())
++ : QRectF(0, 0, 1, 1);
++
++ QSGGeometry::updateTexturedRectGeometry(geometry, rect, normalizedCoordinates);
++
++ node->setGeometry(geometry);
++ node->setFlag(QSGNode::OwnsGeometry, true);
++
++ node->setMaterial(material);
++ node->setFlag(QSGNode::OwnsMaterial, true);
++
++ return node;
++ #else
++ qCWarning(qLcWaylandCompositor) << "Without OpenGL support only shared memory textures are supported";
++ return nullptr;
++ #endif // QT_CONFIG(opengl)
++ }
++
++ void QWaylandQuickItem::setTouchEventsEnabled(bool enabled)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->touchEventsEnabled != enabled) {
++ d->touchEventsEnabled = enabled;
++ emit touchEventsEnabledChanged();
++ }
++ }
++
++ /*!
++ * \qmlproperty bool QtWayland.Compositor::WaylandQuickItem::inputEventsEnabled
++ *
++ * This property holds whether input events are enabled for this item.
++ *
++ * When \c true, the item will process and handle input events such as keyboard
++ * and mouse events; when \c false, input events are ignored.
++ */
++
++ /*!
++ * \property QWaylandQuickItem::inputEventsEnabled
++ *
++ * This property holds whether input events are enabled for this item.
++ *
++ * When \c true, the item will process and handle input events such as keyboard
++ * and mouse events; when \c false, input events are ignored.
++ */
++ bool QWaylandQuickItem::inputEventsEnabled() const
++ {
++ Q_D(const QWaylandQuickItem);
++ return d->inputEventsEnabled;
++ }
++
++ void QWaylandQuickItem::setInputEventsEnabled(bool enabled)
++ {
++ Q_D(QWaylandQuickItem);
++ if (d->inputEventsEnabled != enabled) {
++ if (enabled)
++ setEnabled(true);
++ d->setInputEventsEnabled(enabled);
++ emit inputEventsEnabledChanged();
++ }
++ }
++
++ void QWaylandQuickItem::lower()
++ {
++ Q_D(QWaylandQuickItem);
++ d->lower();
++ }
++
++ void QWaylandQuickItemPrivate::lower()
++ {
++ Q_Q(QWaylandQuickItem);
++ QQuickItem *parent = q->parentItem();
++ Q_ASSERT(parent);
++ QQuickItem *bottom = parent->childItems().constFirst();
++ if (q != bottom)
++ q->stackBefore(bottom);
++ }
++
++ void QWaylandQuickItem::raise()
++ {
++ Q_D(QWaylandQuickItem);
++ d->raise();
++ }
++
++ void QWaylandQuickItemPrivate::raise()
++ {
++ Q_Q(QWaylandQuickItem);
++ QQuickItem *parent = q->parentItem();
++ Q_ASSERT(parent);
++ QQuickItem *top = parent->childItems().constLast();
++ if (q != top)
++ q->stackAfter(top);
++ }
++
++ void QWaylandQuickItem::sendMouseMoveEvent(const QPointF &position, QWaylandSeat *seat)
++ {
++ if (seat == nullptr)
++ seat = compositor()->defaultSeat();
++
++ if (!seat) {
++ qWarning() << "No seat, can't send mouse event";
++ return;
++ }
++
++ seat->sendMouseMoveEvent(view(), position);
++ }
++
++ /*!
++ * \internal
++ *
++ * Sets the position of this item relative to the parent item.
++ */
++ void QWaylandQuickItem::handleSubsurfacePosition(const QPoint &pos)
++ {
++ Q_D(QWaylandQuickItem);
++ QQuickItem::setPosition(pos * d->scaleFactor());
++ }
++
++ #if QT_CONFIG(draganddrop)
++ void QWaylandQuickItem::handleDragStarted(QWaylandDrag *drag)
++ {
++ Q_D(QWaylandQuickItem);
++ Q_ASSERT(drag->origin() == surface());
++ drag->seat()->setMouseFocus(nullptr);
++ d->isDragging = true;
++ }
++ #endif
++
++ qreal QWaylandQuickItemPrivate::scaleFactor() const
++ {
++ qreal f = view->output() ? view->output()->scaleFactor() : 1;
++ #if !defined(Q_OS_MACOS)
++ if (window)
++ f /= window->devicePixelRatio();
++ #endif
++ return f;
++ }
++
++ QWaylandQuickItem *QWaylandQuickItemPrivate::findSibling(QWaylandSurface *surface) const
++ {
++ Q_Q(const QWaylandQuickItem);
++ auto *parent = q->parentItem();
++ if (!parent)
++ return nullptr;
++
++ const auto siblings = q->parentItem()->childItems();
++ for (auto *sibling : siblings) {
++ auto *waylandItem = qobject_cast<QWaylandQuickItem *>(sibling);
++ if (waylandItem && waylandItem->surface() == surface)
++ return waylandItem;
++ }
++ return nullptr;
++ }
++
++ void QWaylandQuickItemPrivate::placeAboveSibling(QWaylandQuickItem *sibling)
++ {
++ Q_Q(QWaylandQuickItem);
++ q->stackAfter(sibling);
++ q->setZ(sibling->z());
++ belowParent = sibling->d_func()->belowParent;
++ }
++
++ void QWaylandQuickItemPrivate::placeBelowSibling(QWaylandQuickItem *sibling)
++ {
++ Q_Q(QWaylandQuickItem);
++ q->stackBefore(sibling);
++ q->setZ(sibling->z());
++ belowParent = sibling->d_func()->belowParent;
++ }
++
++ //### does not handle changes in z value if parent is a subsurface
++ void QWaylandQuickItemPrivate::placeAboveParent()
++ {
++ Q_Q(QWaylandQuickItem);
++ const auto siblings = q->parentItem()->childItems();
++
++ // Stack below first (bottom) sibling above parent
++ bool foundSibling = false;
++ for (auto it = siblings.cbegin(); it != siblings.cend(); ++it) {
++ QWaylandQuickItem *sibling = qobject_cast<QWaylandQuickItem*>(*it);
++ if (sibling && !sibling->d_func()->belowParent) {
++ q->stackBefore(sibling);
++ foundSibling = true;
++ break;
++ }
++ }
++
++ // No other subsurfaces above parent
++ if (!foundSibling && siblings.last() != q)
++ q->stackAfter(siblings.last());
++
++ q->setZ(q->parentItem()->z());
++ belowParent = false;
++ }
++
++ //### does not handle changes in z value if parent is a subsurface
++ void QWaylandQuickItemPrivate::placeBelowParent()
++ {
++ Q_Q(QWaylandQuickItem);
++ const auto siblings = q->parentItem()->childItems();
++
++ // Stack above last (top) sibling below parent
++ bool foundSibling = false;
++ for (auto it = siblings.crbegin(); it != siblings.crend(); ++it) {
++ QWaylandQuickItem *sibling = qobject_cast<QWaylandQuickItem*>(*it);
++ if (sibling && sibling->d_func()->belowParent) {
++ q->stackAfter(sibling);
++ foundSibling = true;
++ break;
++ }
++ }
++
++ // No other subsurfaces below parent
++ if (!foundSibling && siblings.first() != q)
++ q->stackBefore(siblings.first());
++
++ q->setZ(q->parentItem()->z() - 1.0);
++ belowParent = true;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qwaylandquickitem.cpp"