blob: 6c4ca8b133f235c8f48f5e6e2ce57115d43e0c26 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWaylandCompositor module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qwaylandquickitem.h"
#include "qwaylandquickitem_p.h"
#include "qwaylandquicksurface.h"
#include "qwaylandinputmethodcontrol.h"
#include "qwaylandtextinput.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>
#include <QtGui/QKeyEvent>
#include <QtGui/QGuiApplication>
#include <QtGui/QScreen>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLTexture>
#include <QtQuick/QSGSimpleTextureNode>
#include <QtQuick/QQuickWindow>
#include <QtCore/QMutexLocker>
#include <QtCore/QMutex>
#include <wayland-server-core.h>
#include <QThread>
#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;
GLenum textureTarget;
int planeCount;
bool canProvideTexture;
QSGMaterial::Flags materialFlags;
QSGMaterialType materialType;
} bufferTypes[] = {
// BufferFormatEgl_Null
{ "", "", 0, 0, false, 0, {} },
// BufferFormatEgl_RGB
{
":/qt-project.org/wayland/compositor/shaders/surface.vert",
":/qt-project.org/wayland/compositor/shaders/surface_rgbx.frag",
GL_TEXTURE_2D, 1, true,
QSGMaterial::Blending,
{}
},
// BufferFormatEgl_RGBA
{
":/qt-project.org/wayland/compositor/shaders/surface.vert",
":/qt-project.org/wayland/compositor/shaders/surface_rgba.frag",
GL_TEXTURE_2D, 1, true,
QSGMaterial::Blending,
{}
},
// BufferFormatEgl_EXTERNAL_OES
{
":/qt-project.org/wayland/compositor/shaders/surface.vert",
":/qt-project.org/wayland/compositor/shaders/surface_oes_external.frag",
GL_TEXTURE_EXTERNAL_OES, 1, false,
QSGMaterial::Blending,
{}
},
// BufferFormatEgl_Y_U_V
{
":/qt-project.org/wayland/compositor/shaders/surface.vert",
":/qt-project.org/wayland/compositor/shaders/surface_y_u_v.frag",
GL_TEXTURE_2D, 3, false,
QSGMaterial::Blending,
{}
},
// BufferFormatEgl_Y_UV
{
":/qt-project.org/wayland/compositor/shaders/surface.vert",
":/qt-project.org/wayland/compositor/shaders/surface_y_uv.frag",
GL_TEXTURE_2D, 2, false,
QSGMaterial::Blending,
{}
},
// BufferFormatEgl_Y_XUXV
{
":/qt-project.org/wayland/compositor/shaders/surface.vert",
":/qt-project.org/wayland/compositor/shaders/surface_y_xuxv.frag",
GL_TEXTURE_2D, 2, false,
QSGMaterial::Blending,
{}
}
};
QWaylandBufferMaterialShader::QWaylandBufferMaterialShader(QWaylandBufferRef::BufferFormatEgl format)
: m_format(format)
{
setShaderSourceFile(QOpenGLShader::Vertex, QString::fromLatin1(bufferTypes[format].vertexShaderSourceFile));
setShaderSourceFile(QOpenGLShader::Fragment, QString::fromLatin1(bufferTypes[format].fragmentShaderSourceFile));
}
void QWaylandBufferMaterialShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
QSGMaterialShader::updateState(state, newEffect, oldEffect);
QWaylandBufferMaterial *material = static_cast<QWaylandBufferMaterial *>(newEffect);
material->bind();
if (state.isMatrixDirty())
program()->setUniformValue(m_id_matrix, state.combinedMatrix());
if (state.isOpacityDirty())
program()->setUniformValue(m_id_opacity, state.opacity());
}
const char * const *QWaylandBufferMaterialShader::attributeNames() const
{
static char const *const attr[] = { "qt_VertexPosition", "qt_VertexTexCoord", nullptr };
return attr;
}
void QWaylandBufferMaterialShader::initialize()
{
QSGMaterialShader::initialize();
m_id_matrix = program()->uniformLocation("qt_Matrix");
m_id_opacity = program()->uniformLocation("qt_Opacity");
for (int i = 0; i < bufferTypes[m_format].planeCount; i++) {
m_id_tex << program()->uniformLocation("tex" + QByteArray::number(i));
program()->setUniformValue(m_id_tex[i], i);
}
Q_ASSERT(m_id_tex.size() == bufferTypes[m_format].planeCount);
}
QWaylandBufferMaterial::QWaylandBufferMaterial(QWaylandBufferRef::BufferFormatEgl format)
: m_format(format)
{
QOpenGLFunctions *gl = QOpenGLContext::currentContext()->functions();
gl->glBindTexture(bufferTypes[m_format].textureTarget, 0);
setFlag(bufferTypes[m_format].materialFlags);
}
QWaylandBufferMaterial::~QWaylandBufferMaterial()
{
}
void QWaylandBufferMaterial::setTextureForPlane(int plane, QOpenGLTexture *texture)
{
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;
else
m_textures[plane] = texture;
}
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() const
{
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;
}
}
#endif // QT_CONFIG(opengl)
QMutex *QWaylandQuickItemPrivate::mutex = nullptr;
class QWaylandSurfaceTextureProvider : public QSGTextureProvider
{
public:
QWaylandSurfaceTextureProvider()
{
}
~QWaylandSurfaceTextureProvider() override
{
if (m_sgTex)
m_sgTex->deleteLater();
}
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());
#if QT_CONFIG(opengl)
if (m_sgTex)
m_sgTex->bind();
#endif
} else {
#if QT_CONFIG(opengl)
QQuickWindow::CreateTextureOptions opt;
QWaylandQuickSurface *surface = qobject_cast<QWaylandQuickSurface *>(surfaceItem->surface());
if (surface && surface->useTextureAlpha()) {
opt |= QQuickWindow::TextureHasAlphaChannel;
}
auto texture = buffer.toOpenGLTexture();
auto size = surface->bufferSize();
m_sgTex = surfaceItem->window()->createTextureFromId(texture->textureId(), 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; }
private:
bool m_smooth = false;
QSGTexture *m_sgTex = nullptr;
QWaylandBufferRef m_ref;
};
/*!
* \qmltype WaylandQuickItem
* \inqmlmodule QtWayland.Compositor
* \since 5.8
* \brief Provides a Qt Quick item that represents a WaylandView.
*
* 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 representing a QWaylandView.
*
* 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)
: QQuickItem(*new QWaylandQuickItemPrivate(), parent)
{
d_func()->init();
}
/*!
* \internal
*/
QWaylandQuickItem::QWaylandQuickItem(QWaylandQuickItemPrivate &dd, QQuickItem *parent)
: QQuickItem(dd, parent)
{
d_func()->init();
}
/*!
* Destroy the QWaylandQuickItem.
*/
QWaylandQuickItem::~QWaylandQuickItem()
{
Q_D(QWaylandQuickItem);
disconnect(this, &QQuickItem::windowChanged, this, &QWaylandQuickItem::updateWindow);
QMutexLocker locker(d->mutex);
if (d->provider)
d->provider->deleteLater();
}
/*!
* \qmlproperty WaylandCompositor QtWaylandCompositor::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 QtWaylandCompositor::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);
QWaylandCompositor *oldComp = d->view->surface() ? d->view->surface()->compositor() : nullptr;
d->view->setSurface(surface);
QWaylandCompositor *newComp = d->view->surface() ? d->view->surface()->compositor() : nullptr;
if (oldComp != newComp)
emit compositorChanged();
update();
}
/*!
* \qmlproperty enum QtWaylandCompositor::WaylandQuickItem::origin
*
* This property holds the origin of the QWaylandQuickItem.
*/
/*!
* \property QWaylandQuickItem::origin
*
* This property holds the origin of the QWaylandQuickItem.
*/
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->localPos())) {
event->ignore();
return;
}
QWaylandSeat *seat = compositor()->seatFor(event);
if (d->focusOnClick)
takeFocus(seat);
seat->sendMouseMoveEvent(d->view.data(), mapToSurface(event->localPos()), event->windowPos());
seat->sendMousePressEvent(event->button());
d->hoverPos = event->localPos();
}
/*!
* \internal
*/
void QWaylandQuickItem::mouseMoveEvent(QMouseEvent *event)
{
Q_D(QWaylandQuickItem);
if (d->shouldSendInputEvents()) {
QWaylandSeat *seat = compositor()->seatFor(event);
#if QT_CONFIG(draganddrop)
if (d->isDragging) {
QWaylandQuickOutput *currentOutput = qobject_cast<QWaylandQuickOutput *>(view()->output());
//TODO: also check if dragging onto other outputs
QWaylandQuickItem *targetItem = qobject_cast<QWaylandQuickItem *>(currentOutput->pickClickableItem(mapToScene(event->localPos())));
QWaylandSurface *targetSurface = targetItem ? targetItem->surface() : nullptr;
if (targetSurface) {
QPointF position = mapToItem(targetItem, event->localPos());
QPointF surfacePosition = targetItem->mapToSurface(position);
seat->drag()->dragMove(targetSurface, surfacePosition);
}
} else
#endif // QT_CONFIG(draganddrop)
{
seat->sendMouseMoveEvent(d->view.data(), mapToSurface(event->localPos()), event->windowPos());
d->hoverPos = event->localPos();
}
} else {
emit mouseMove(event->windowPos());
event->ignore();
}
}
/*!
* \internal
*/
void QWaylandQuickItem::mouseReleaseEvent(QMouseEvent *event)
{
Q_D(QWaylandQuickItem);
if (d->shouldSendInputEvents()) {
QWaylandSeat *seat = compositor()->seatFor(event);
#if QT_CONFIG(draganddrop)
if (d->isDragging) {
d->isDragging = false;
seat->drag()->drop();
} else
#endif
{
seat->sendMouseReleaseEvent(event->button());
}
} else {
emit mouseRelease();
event->ignore();
}
}
/*!
* \internal
*/
void QWaylandQuickItem::hoverEnterEvent(QHoverEvent *event)
{
Q_D(QWaylandQuickItem);
if (!inputRegionContains(event->posF())) {
event->ignore();
return;
}
if (d->shouldSendInputEvents()) {
QWaylandSeat *seat = compositor()->seatFor(event);
seat->sendMouseMoveEvent(d->view.data(), event->posF(), mapToScene(event->posF()));
d->hoverPos = event->posF();
} else {
event->ignore();
}
}
/*!
* \internal
*/
void QWaylandQuickItem::hoverMoveEvent(QHoverEvent *event)
{
Q_D(QWaylandQuickItem);
if (surface()) {
if (!inputRegionContains(event->posF())) {
event->ignore();
return;
}
}
if (d->shouldSendInputEvents()) {
QWaylandSeat *seat = compositor()->seatFor(event);
if (event->posF() != d->hoverPos) {
seat->sendMouseMoveEvent(d->view.data(), mapToSurface(event->posF()), mapToScene(event->posF()));
d->hoverPos = event->posF();
}
} else {
event->ignore();
}
}
/*!
* \internal
*/
void QWaylandQuickItem::hoverLeaveEvent(QHoverEvent *event)
{
Q_D(QWaylandQuickItem);
if (d->shouldSendInputEvents()) {
QWaylandSeat *seat = compositor()->seatFor(event);
seat->setMouseFocus(nullptr);
} else {
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() && hasFocus()) {
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->touchPoints();
if (!points.isEmpty())
pointPos = points.at(0).pos();
if (event->type() == QEvent::TouchBegin && !inputRegionContains(pointPos)) {
event->ignore();
return;
}
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) {
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 : 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);
connect(childSurface, &QWaylandSurface::subsurfacePositionChanged, childItem, &QWaylandQuickItem::handleSubsurfacePosition);
} else {
bool success = QMetaObject::invokeMethod(d->subsurfaceHandler, "handleSubsurfaceAdded", Q_ARG(QWaylandSurface *, 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";
}
}
/*!
\qmlproperty object QtWaylandCompositor::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.
*/
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();
}
}
/*!
* \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 QtWaylandCompositor::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 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::hasContentChanged, this, &QWaylandQuickItem::surfaceMappedChanged);
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::hasContentChanged, this, &QWaylandQuickItem::surfaceMappedChanged);
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 (auto subsurface : 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())
return;
QWaylandSeat *target = device;
if (!target) {
target = compositor()->defaultSeat();
}
target->setKeyboardFocus(surface());
QWaylandTextInput *textInput = QWaylandTextInput::findIn(target);
if (textInput)
textInput->setFocus(surface());
}
/*!
* \internal
*/
void QWaylandQuickItem::surfaceMappedChanged()
{
update();
}
/*!
* \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);
QSize size(0, 0);
if (surface())
size = surface()->destinationSize() * d->scaleFactor();
setImplicitSize(size.width(), size.height());
if (d->sizeFollowsSurface)
setSize(size);
}
/*!
* \qmlproperty bool QtWaylandCompositor::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;
}
// Qt 6: Remove the non-const version
/*!
* 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)
{
return const_cast<const QWaylandQuickItem *>(this)->inputRegionContains(localPosition);
}
/*!
* \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);
}
/*!
* \qmlproperty bool QtWaylandCompositor::WaylandQuickItem::sizeFollowsSurface
*
* This property specifies whether the size of the item should always match
* the size of its surface.
*
* The default is \c true.
*/
/*!
* \property QWaylandQuickItem::sizeFollowsSurface
*
* This property specifies whether the size of the item should always match
* the size of its surface.
*
* The default is \c true.
*/
bool QWaylandQuickItem::sizeFollowsSurface() const
{
Q_D(const QWaylandQuickItem);
return d->sizeFollowsSurface;
}
//TODO: sizeFollowsSurface became obsolete when we added an implementation for
//implicit size. The property is here for compatibility reasons only and should
//be removed or at least default to false in Qt 6.
void QWaylandQuickItem::setSizeFollowsSurface(bool sizeFollowsSurface)
{
Q_D(QWaylandQuickItem);
if (d->sizeFollowsSurface == sizeFollowsSurface)
return;
d->sizeFollowsSurface = sizeFollowsSurface;
emit sizeFollowsSurfaceChanged();
}
#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 QtWaylandCompositor::WaylandQuickItem::paintEnabled
Returns true if the item is hidden, though the texture
is still updated. As opposed to hiding the item by
setting \l{Item::visible}{visible} to \c false, setting this property to \c false
will not prevent mouse or keyboard input from reaching item.
*/
/*!
\property QWaylandQuickItem::paintEnabled
Holds \c true if the item is hidden, though the texture
is still updated. As opposed to hiding the item by
setting \l{QQuickItem::}{visible} to \c false, setting this property to \c false
will not prevent mouse or keyboard input from reaching item.
*/
bool QWaylandQuickItem::paintEnabled() const
{
Q_D(const QWaylandQuickItem);
return d->paintEnabled;
}
void QWaylandQuickItem::setPaintEnabled(bool enabled)
{
Q_D(QWaylandQuickItem);
d->paintEnabled = enabled;
update();
}
/*!
\qmlproperty bool QtWaylandCompositor::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() && d->connectedWindow) {
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 QtWaylandCompositor::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() && !bufferHasContent && 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
) {
// 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();
d->newTexture = true;
}
if (!d->provider)
d->provider = new QWaylandSurfaceTextureProvider();
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);
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;
for (int plane = 0; plane < bufferTypes[ref.bufferFormatEgl()].planeCount; plane++)
if (auto texture = ref.toOpenGLTexture(plane))
material->setTextureForPlane(plane, texture);
material->bind();
}
QSGGeometry::updateTexturedRectGeometry(geometry, rect, QRectF(0, 0, 1, 1));
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();
}
}
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()
{
QQuickItem *parent = parentItem();
Q_ASSERT(parent);
QQuickItem *bottom = parent->childItems().first();
if (this != bottom)
stackBefore(bottom);
}
void QWaylandQuickItem::raise()
{
QQuickItem *parent = parentItem();
Q_ASSERT(parent);
QQuickItem *top = parent->childItems().last();
if (this != top)
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