diff options
133 files changed, 7623 insertions, 380 deletions
diff --git a/.qmake.conf b/.qmake.conf index f8cda0e7d..db2e0b653 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,3 +1,3 @@ load(qt_build_config) -MODULE_VERSION = 5.13.0 +MODULE_VERSION = 5.14.0 diff --git a/config.tests/vulkan_server_buffer/main.cpp b/config.tests/vulkan_server_buffer/main.cpp new file mode 100644 index 000000000..5bd88d338 --- /dev/null +++ b/config.tests/vulkan_server_buffer/main.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Compositor. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <vulkan/vulkan.h> + +int main() +{ + VkExportMemoryAllocateInfoKHR exportAllocInfo = {}; + exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR; + exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR; + + return 0; +} diff --git a/config.tests/vulkan_server_buffer/vulkan_server_buffer.pro b/config.tests/vulkan_server_buffer/vulkan_server_buffer.pro new file mode 100644 index 000000000..28dcadcbf --- /dev/null +++ b/config.tests/vulkan_server_buffer/vulkan_server_buffer.pro @@ -0,0 +1 @@ +SOURCES += main.cpp diff --git a/examples/wayland/server-buffer/README b/examples/wayland/server-buffer/README index 5744a6baf..da20b0f50 100644 --- a/examples/wayland/server-buffer/README +++ b/examples/wayland/server-buffer/README @@ -1,4 +1,7 @@ -This is the example to demonstrate the server buffer interfaces +This example shows how to use the low-level server buffer extension. This +version of Qt also provides a texture sharing extension that provides more +functionality and convenience for sharing graphical assets with Qt Quick +clients: see the texture-sharing example. Compile up both compositor and client. diff --git a/examples/wayland/texture-sharing/.gitignore b/examples/wayland/texture-sharing/.gitignore new file mode 100644 index 000000000..c684448d3 --- /dev/null +++ b/examples/wayland/texture-sharing/.gitignore @@ -0,0 +1,2 @@ +custom-compositor/custom-compositor +qml-client/qml-client diff --git a/examples/wayland/texture-sharing/README b/examples/wayland/texture-sharing/README new file mode 100644 index 000000000..27ea76745 --- /dev/null +++ b/examples/wayland/texture-sharing/README @@ -0,0 +1,27 @@ +This example shows how to use the texture sharing extension, allowing +multiple clients to share the same copy of an image in graphics memory. + +The texture sharing extension uses the server buffer extension to transport +graphics buffers. There are different server buffer plugins for different +graphics hardware. This is specified by setting an environment variable for +the compositor process. + +-On a device with Mesa and Intel integrated graphics, set: + + QT_WAYLAND_SERVER_BUFFER_INTEGRATION=dmabuf-server + +-On a device with NVIDIA graphics, set: + + QT_WAYLAND_SERVER_BUFFER_INTEGRATION=vulkan-server + +'custom-compositor' shows how to write a server that creates shared textures +programmatically. + +The file 'minimal-compositor.qml' shows how to add texture sharing to an +existing compositor, using only QML. It is based on the minimal-qml example, +and can be executed with qmlscene. + +'qml-client' shows how to use shared textures in a Qt Quick client. +The compositor uses the hardware integration extension to broadcast +the name of the server buffer integration to all clients, so qml-client +can be started like any normal wayland client. diff --git a/examples/wayland/texture-sharing/custom-compositor/compositor.qrc b/examples/wayland/texture-sharing/custom-compositor/compositor.qrc new file mode 100644 index 000000000..86a8567f7 --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/compositor.qrc @@ -0,0 +1,9 @@ +<RCC> + <qresource prefix="/"> + <file>images/background.png</file> + <file>images/qt_logo.png</file> + <file>images/qt4.astc</file> + <file>images/car.ktx</file> + <file>qml/main.qml</file> + </qresource> +</RCC> diff --git a/examples/wayland/texture-sharing/custom-compositor/custom-compositor.pro b/examples/wayland/texture-sharing/custom-compositor/custom-compositor.pro new file mode 100644 index 000000000..e80e9e153 --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/custom-compositor.pro @@ -0,0 +1,18 @@ +QT += core gui qml + +QT += waylandcompositor-private + +SOURCES += \ + main.cpp + +OTHER_FILES = \ + qml/main.qml \ + qml/Screen.qml \ + images/background.jpg + +RESOURCES += compositor.qrc + +TARGET = custom-compositor + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/texture-sharing/custom-compositor +INSTALLS += target diff --git a/examples/wayland/texture-sharing/custom-compositor/images/background.png b/examples/wayland/texture-sharing/custom-compositor/images/background.png Binary files differnew file mode 100644 index 000000000..845830c59 --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/images/background.png diff --git a/examples/wayland/texture-sharing/custom-compositor/images/car.ktx b/examples/wayland/texture-sharing/custom-compositor/images/car.ktx Binary files differnew file mode 100644 index 000000000..2aefdd306 --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/images/car.ktx diff --git a/examples/wayland/texture-sharing/custom-compositor/images/qt4.astc b/examples/wayland/texture-sharing/custom-compositor/images/qt4.astc Binary files differnew file mode 100644 index 000000000..7f7a3f473 --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/images/qt4.astc diff --git a/examples/wayland/texture-sharing/custom-compositor/images/qt_logo.png b/examples/wayland/texture-sharing/custom-compositor/images/qt_logo.png Binary files differnew file mode 100644 index 000000000..5e2b355ea --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/images/qt_logo.png diff --git a/examples/wayland/texture-sharing/custom-compositor/main.cpp b/examples/wayland/texture-sharing/custom-compositor/main.cpp new file mode 100644 index 000000000..a39c8c381 --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/main.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Wayland module +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QUrl> +#include <QtCore/QDebug> +#include <QtGui/QGuiApplication> +#include <QtQml/QQmlApplicationEngine> + +#include <QtQml/qqml.h> +#include <QtQml/QQmlEngine> + +#include <QtGui/QPainter> +#include <QtGui/QImage> + +#include <QtCore/QDateTime> + +#include "QtWaylandCompositor/private/qwltexturesharingextension_p.h" + +class CustomSharingExtension : public QWaylandTextureSharingExtension +{ + Q_OBJECT +public: + CustomSharingExtension() {qDebug("Instantiating custom texture sharing extension.");} +protected: + bool customPixelData(const QString &key, QByteArray *data, QSize *size, uint *glInternalFormat) override + { + qDebug() << "CustomSharingExtension looking for local texture data for" << key; + if (key.startsWith("unreasonably large ")) { + int w = 10000; + int h = 10000; + int numBytes = w * h * 4; + *data = QByteArray(numBytes, 0); + quint32 *pixels = reinterpret_cast<quint32*>(data->data()); + for (int i = 0; i < w*h; ++i) + pixels[i] = 0xff7f1fff; + *glInternalFormat = GL_RGBA8; + *size = QSize(w,h); + return true; + } + + QImage img; + + if (key == QLatin1String("test pattern 1")) { + img = QImage(128,128,QImage::Format_ARGB32_Premultiplied); + img.fill(QColor(0x55,0x0,0x55,0x01)); + { + QPainter p(&img); + QPen pen = p.pen(); + pen.setWidthF(3); + pen.setColor(Qt::red); + p.setPen(pen); + p.drawLine(0,0,128,128); + pen.setColor(Qt::green); + p.setPen(pen); + p.drawLine(128,0,0,128); + pen.setColor(Qt::blue); + p.setPen(pen); + p.drawLine(32,16,96,16); + pen.setColor(Qt::black); + p.setPen(pen); + p.translate(64, 64); + p.rotate(45); + p.drawText(QRect(-48, -32, 96, 64), + QDateTime::currentDateTime().toString(), + QTextOption(Qt::AlignHCenter)); + } + } + + if (!img.isNull()) { + img = img.convertToFormat(QImage::Format_RGBA8888); + *data = QByteArray(reinterpret_cast<const char*>(img.constBits()), img.sizeInBytes()); + *size = img.size(); + *glInternalFormat = GL_RGBA8; + return true; + } + return false; + } +}; + +Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(CustomSharingExtension); + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + QGuiApplication app(argc, argv); + QQmlApplicationEngine appEngine; + + qmlRegisterType<CustomSharingExtensionQuickExtension>("com.theqtcompany.customsharingextension", 1, 0, "CustomSharingExtension"); + appEngine.addImageProvider("wlshared", new QWaylandSharedTextureProvider); + + appEngine.load(QUrl("qrc:///qml/main.qml")); + + return app.exec(); +} + +#include "main.moc" diff --git a/examples/wayland/texture-sharing/custom-compositor/qml/main.qml b/examples/wayland/texture-sharing/custom-compositor/qml/main.qml new file mode 100644 index 000000000..16a412fcd --- /dev/null +++ b/examples/wayland/texture-sharing/custom-compositor/qml/main.qml @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Wayland module +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Window 2.2 +import QtWayland.Compositor 1.3 + +import com.theqtcompany.customsharingextension 1.0 + +WaylandCompositor { + WaylandOutput { + sizeFollowsWindow: true + window: Window { + width: 1024 + height: 768 + visible: true + Image { + id: background + anchors.fill: parent + fillMode: Image.Tile + source: "qrc:/images/background.png" + smooth: true + + Rectangle { + width: 100 + height: 100 + color: "red" + anchors.bottom: parent.bottom; + anchors.right: parent.right; + MouseArea { + anchors.fill: parent + onClicked: sharedTextureImage.source = "image://wlshared/car.ktx" + } + } + Image { + id: sharedTextureImage + anchors.bottom: parent.bottom; + anchors.right: parent.right; + source: "" + } + Image { + id: topRightImage + anchors.top: parent.top; + anchors.right: parent.right; + source: "image://wlshared/qt_logo.png" + } + } + Repeater { + model: shellSurfaces + ShellSurfaceItem { + autoCreatePopupItems: true + shellSurface: modelData + onSurfaceDestroyed: shellSurfaces.remove(index) + } + } + } + } + WlShell { + onWlShellSurfaceCreated: + shellSurfaces.append({shellSurface: shellSurface}); + } + XdgShellV6 { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } + XdgShell { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } + ListModel { id: shellSurfaces } + + CustomSharingExtension { + imageSearchPath: ":/images;." + } +} diff --git a/examples/wayland/texture-sharing/minimal-compositor.qml b/examples/wayland/texture-sharing/minimal-compositor.qml new file mode 100644 index 000000000..3f714dc58 --- /dev/null +++ b/examples/wayland/texture-sharing/minimal-compositor.qml @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Window 2.2 +import QtWayland.Compositor 1.3 + +// importing the texture sharing extension: +import QtWayland.Compositor.TextureSharingExtension 1.0 + +WaylandCompositor { + WaylandOutput { + sizeFollowsWindow: true + window: Window { + width: 1024 + height: 768 + visible: true + Repeater { + model: shellSurfaces + ShellSurfaceItem { + autoCreatePopupItems: true + shellSurface: modelData + onSurfaceDestroyed: shellSurfaces.remove(index) + } + } + } + } + WlShell { + onWlShellSurfaceCreated: + shellSurfaces.append({shellSurface: shellSurface}); + } + XdgShellV6 { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } + XdgShell { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } + ListModel { id: shellSurfaces } + + // instantiating the texture sharing extension: + TextureSharingExtension { + imageSearchPath: ".;/tmp;/usr/share/pixmaps" + } +} diff --git a/examples/wayland/texture-sharing/qml-client/main.cpp b/examples/wayland/texture-sharing/qml-client/main.cpp new file mode 100644 index 000000000..618d6701d --- /dev/null +++ b/examples/wayland/texture-sharing/qml-client/main.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** + ** + ** Copyright (C) 2019 The Qt Company Ltd. + ** Contact: https://www.qt.io/licensing/ + ** + ** This file is part of the examples of the Qt Wayland module + ** + ** $QT_BEGIN_LICENSE:BSD$ + ** 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. + ** + ** BSD License Usage + ** Alternatively, you may use this file under the terms of the BSD license + ** as follows: + ** + ** "Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are + ** met: + ** * Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** * Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in + ** the documentation and/or other materials provided with the + ** distribution. + ** * Neither the name of The Qt Company Ltd nor the names of its + ** contributors may be used to endorse or promote products derived + ** from this software without specific prior written permission. + ** + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include <QGuiApplication> +#include <QtQuick/QQuickView> +#include <QStandardPaths> +#include <QFileInfo> +#include <QQmlApplicationEngine> +#include <QDebug> +#include <QDir> +#include <QTimer> + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + QQmlApplicationEngine appEngine; + + appEngine.load(QUrl("qrc:///main.qml")); + + return app.exec(); +} diff --git a/examples/wayland/texture-sharing/qml-client/main.qml b/examples/wayland/texture-sharing/qml-client/main.qml new file mode 100644 index 000000000..371a97594 --- /dev/null +++ b/examples/wayland/texture-sharing/qml-client/main.qml @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Wayland module +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.9 +import QtQuick.Window 2.2 + +import QtWayland.Client.TextureSharing 1.0 + +Window { + width: 800 + height: 500 + visible: true + + Rectangle { + anchors.fill: parent + color: "#C0FEFE" + + Flickable { + anchors.fill: parent + contentHeight: imageGrid.height + + Grid { + id: imageGrid + columns: 2 + width: parent.width + spacing: 25 + padding: 25 + + + // loadedImage + Text { + width: 400 + wrapMode: Text.Wrap + text: "An Image element using the shared buffer provider to load from a PNG image.<br>" + + "Source: '" + loadedImage.source + "'" + + (loadedImage.sourceSize.height <= 0 ? "<font color=\"#FF0000\"><br>[Image not loaded]</font>" : "") + } + Image { + id: loadedImage + fillMode: Image.PreserveAspectFit + source: "image://wlshared/qt_logo.png" + } + Rectangle { + visible: loadedImage.height <= 0 + width:100; height: 100 + color: "green" + } + + // paintedImage + Text { + width: 400 + wrapMode: Text.Wrap + text: "An Image element using the shared buffer provider.<br>" + + "This texture is created by the compositor using QPainter. <br>" + + "Source: '" + paintedImage.source + "'" + + (paintedImage.sourceSize.height <= 0 ? "<font color=\"#FF0000\"><br>[Image not loaded]</font>" : "") + } + Image { + id: paintedImage + fillMode: Image.PreserveAspectFit + source: "image://wlshared/test pattern 1" + } + Rectangle { + visible: paintedImage.height <= 0 + width:100; height: 100 + color: "green" + } + + // ktxImage + Text { + width: 400 + wrapMode: Text.Wrap + text: "An Image element using the shared buffer provider to load an ETC2 compressed texture." + + "<br>Source: '" + ktxImage.source + "'" + + (ktxImage.sourceSize.height <= 0 ? "<font color=\"#FF0000\"><br>[Image not loaded]</font>" : "") + } + Image { + id: ktxImage + source: "image://wlshared/car.ktx" + fillMode: Image.PreserveAspectFit + } + Rectangle { + visible: ktxImage.height <= 0 + width:100; height: 100 + color: "green" + } + + //astcImage + Text { + width: 400 + wrapMode: Text.Wrap + text: "An Image element using the shared buffer provider to load an ASTC compressed texture." + + "<br>Source: '" + astcImage.source + "'" + + (astcImage.sourceSize.height <= 0 ? "<font color=\"#FF0000\"><br>[Image not loaded]</font>" : "") + } + + Image { + id: astcImage + source: "image://wlshared/qt4.astc" + fillMode: Image.PreserveAspectFit + } + Rectangle { + visible: astcImage.height <= 0 + width:100; height: 100 + color: "green" + } + + // dynamicImage + Column { + Text { + width: 400 + wrapMode: Text.Wrap + text: "An Image element using the shared buffer provider." + + "<br>Source: '" + dynamicImage.source + "'" + + (dynamicImage.sourceSize.height <= 0 ? "<font color=\"#FF0000\"><br>[Image not loaded]</font>" : "") + } + Row { + spacing: 10 + Text { + text: "Enter filename:" + } + Rectangle { + color: "white" + width: sourceEdit.contentWidth + 30 + height: sourceEdit.contentHeight + TextInput { + id: sourceEdit + anchors.fill: parent + horizontalAlignment: TextInput.AlignHCenter + onEditingFinished: dynamicImage.source = text ? "image://wlshared/" + text : "" + } + } + } + } + Image { + id: dynamicImage + fillMode: Image.PreserveAspectFit + } + Rectangle { + visible: dynamicImage.height <= 0 + width:100; height: 100 + color: "green" + } + + // largeImage + Text { + width: 400 + wrapMode: Text.Wrap + text: "An Image element using the shared buffer provider.<br>" + + "Left click to load a very large image. " + + "Right click to unload the image, potentially freeing graphics memory on the server-side " + + "if no other client is using the image." + + "<br>Source: '" + largeImage.source + "'" + + "<br>Size: " + largeImage.sourceSize + + (largeImage.sourceSize.height <= 0 ? "<font color=\"#FF0000\"><br>[Image not loaded]</font>" : "") + } + + Rectangle { + width: 200 + height: 200 + border.color: "black" + border.width: 2 + color: "transparent" + Image { + id: largeImage + anchors.fill: parent + fillMode: Image.PreserveAspectFit + } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button == Qt.LeftButton) + largeImage.source = "image://wlshared/unreasonably large image" + else + largeImage.source = "" + + } + } + } + + } // Grid + } + + Rectangle { + color: "gray" + width: parent.width + height: 20 + anchors.bottom: parent.bottom + + Text { + color: "white" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: "Scroll or drag for more" + } + } + + } +} diff --git a/examples/wayland/texture-sharing/qml-client/qml-client.pro b/examples/wayland/texture-sharing/qml-client/qml-client.pro new file mode 100644 index 000000000..67d5c7071 --- /dev/null +++ b/examples/wayland/texture-sharing/qml-client/qml-client.pro @@ -0,0 +1,13 @@ +QT += quick + +SOURCES += \ + main.cpp + +RESOURCES += \ + qml-client.qrc + +DISTFILES += \ + main.qml + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/texture-sharing/qml-client +INSTALLS += target diff --git a/examples/wayland/texture-sharing/qml-client/qml-client.qrc b/examples/wayland/texture-sharing/qml-client/qml-client.qrc new file mode 100644 index 000000000..5f6483ac3 --- /dev/null +++ b/examples/wayland/texture-sharing/qml-client/qml-client.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>main.qml</file> + </qresource> +</RCC> diff --git a/examples/wayland/texture-sharing/texture-sharing.pro b/examples/wayland/texture-sharing/texture-sharing.pro new file mode 100644 index 000000000..3f7792828 --- /dev/null +++ b/examples/wayland/texture-sharing/texture-sharing.pro @@ -0,0 +1,5 @@ +TEMPLATE=subdirs + +SUBDIRS += \ + qml-client \ + custom-compositor diff --git a/examples/wayland/wayland.pro b/examples/wayland/wayland.pro index b9e4263e7..eeb962eb2 100644 --- a/examples/wayland/wayland.pro +++ b/examples/wayland/wayland.pro @@ -1,10 +1,11 @@ requires(qtHaveModule(waylandcompositor)) -requires(qtConfig(opengl)) TEMPLATE=subdirs -SUBDIRS += \ - qwindow-compositor \ - minimal-cpp +qtHaveModule(opengl) { + SUBDIRS += \ + qwindow-compositor \ + minimal-cpp +} qtHaveModule(quick) { SUBDIRS += minimal-qml @@ -17,8 +18,13 @@ qtHaveModule(quick) { SUBDIRS += server-side-decoration qtHaveModule(waylandclient) { SUBDIRS += \ - custom-extension \ - server-buffer + custom-extension + + qtHaveModule(opengl) { + SUBDIRS += \ + server-buffer \ + texture-sharing + } } SUBDIRS += hwlayer-compositor } diff --git a/src/3rdparty/protocol/qt_attribution.json b/src/3rdparty/protocol/qt_attribution.json index 7e068f755..e6f90698b 100644 --- a/src/3rdparty/protocol/qt_attribution.json +++ b/src/3rdparty/protocol/qt_attribution.json @@ -56,6 +56,23 @@ Copyright (c) 2013 BMW Car IT GmbH" }, { + "Id": "wayland-primary-selection-protocol", + "Name": "Wayland Primary Selection Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "wp-primary-selection-unstable-v1.xml", + + "Description": "The primary selection extension allows copying text by selecting it and pasting it with the middle mouse button.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://cgit.freedesktop.org/wayland/wayland-protocols/plain/unstable/primary-selection/primary-selection-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "MIT_LICENSE.txt", + "Copyright": "Copyright © 2015 2016 Red Hat" + }, + + { "Id": "wayland-scaler-protocol", "Name": "Wayland Scaler Protocol", "QDocModule": "qtwaylandcompositor", diff --git a/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml b/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml new file mode 100644 index 000000000..e5a39e34c --- /dev/null +++ b/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml @@ -0,0 +1,225 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wp_primary_selection_unstable_v1"> + <copyright> + Copyright © 2015, 2016 Red Hat + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <description summary="Primary selection protocol"> + This protocol provides the ability to have a primary selection device to + match that of the X server. This primary selection is a shortcut to the + common clipboard selection, where text just needs to be selected in order + to allow copying it elsewhere. The de facto way to perform this action + is the middle mouse button, although it is not limited to this one. + + Clients wishing to honor primary selection should create a primary + selection source and set it as the selection through + wp_primary_selection_device.set_selection whenever the text selection + changes. In order to minimize calls in pointer-driven text selection, + it should happen only once after the operation finished. Similarly, + a NULL source should be set when text is unselected. + + wp_primary_selection_offer objects are first announced through the + wp_primary_selection_device.data_offer event. Immediately after this event, + the primary data offer will emit wp_primary_selection_offer.offer events + to let know of the mime types being offered. + + When the primary selection changes, the client with the keyboard focus + will receive wp_primary_selection_device.selection events. Only the client + with the keyboard focus will receive such events with a non-NULL + wp_primary_selection_offer. Across keyboard focus changes, previously + focused clients will receive wp_primary_selection_device.events with a + NULL wp_primary_selection_offer. + + In order to request the primary selection data, the client must pass + a recent serial pertaining to the press event that is triggering the + operation, if the compositor deems the serial valid and recent, the + wp_primary_selection_source.send event will happen in the other end + to let the transfer begin. The client owning the primary selection + should write the requested data, and close the file descriptor + immediately. + + If the primary selection owner client disappeared during the transfer, + the client reading the data will receive a + wp_primary_selection_device.selection event with a NULL + wp_primary_selection_offer, the client should take this as a hint + to finish the reads related to the no longer existing offer. + + The primary selection owner should be checking for errors during + writes, merely cancelling the ongoing transfer if any happened. + </description> + + <interface name="zwp_primary_selection_device_manager_v1" version="1"> + <description summary="X primary selection emulation"> + The primary selection device manager is a singleton global object that + provides access to the primary selection. It allows to create + wp_primary_selection_source objects, as well as retrieving the per-seat + wp_primary_selection_device objects. + </description> + + <request name="create_source"> + <description summary="create a new primary selection source"> + Create a new primary selection source. + </description> + <arg name="id" type="new_id" interface="zwp_primary_selection_source_v1"/> + </request> + + <request name="get_device"> + <description summary="create a new primary selection device"> + Create a new data device for a given seat. + </description> + <arg name="id" type="new_id" interface="zwp_primary_selection_device_v1"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection device manager"> + Destroy the primary selection device manager. + </description> + </request> + </interface> + + <interface name="zwp_primary_selection_device_v1" version="1"> + <request name="set_selection"> + <description summary="set the primary selection"> + Replaces the current selection. The previous owner of the primary + selection will receive a wp_primary_selection_source.cancelled event. + + To unset the selection, set the source to NULL. + </description> + <arg name="source" type="object" interface="zwp_primary_selection_source_v1" allow-null="true"/> + <arg name="serial" type="uint" summary="serial of the event that triggered this request"/> + </request> + + <event name="data_offer"> + <description summary="introduce a new wp_primary_selection_offer"> + Introduces a new wp_primary_selection_offer object that may be used + to receive the current primary selection. Immediately following this + event, the new wp_primary_selection_offer object will send + wp_primary_selection_offer.offer events to describe the offered mime + types. + </description> + <arg name="offer" type="new_id" interface="zwp_primary_selection_offer_v1"/> + </event> + + <event name="selection"> + <description summary="advertise a new primary selection"> + The wp_primary_selection_device.selection event is sent to notify the + client of a new primary selection. This event is sent after the + wp_primary_selection.data_offer event introducing this object, and after + the offer has announced its mimetypes through + wp_primary_selection_offer.offer. + + The data_offer is valid until a new offer or NULL is received + or until the client loses keyboard focus. The client must destroy the + previous selection data_offer, if any, upon receiving this event. + </description> + <arg name="id" type="object" interface="zwp_primary_selection_offer_v1" allow-null="true"/> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection device"> + Destroy the primary selection device. + </description> + </request> + </interface> + + <interface name="zwp_primary_selection_offer_v1" version="1"> + <description summary="offer to transfer primary selection contents"> + A wp_primary_selection_offer represents an offer to transfer the contents + of the primary selection clipboard to the client. Similar to + wl_data_offer, the offer also describes the mime types that the data can + be converted to and provides the mechanisms for transferring the data + directly to the client. + </description> + + <request name="receive"> + <description summary="request that the data is transferred"> + To transfer the contents of the primary selection clipboard, the client + issues this request and indicates the mime type that it wants to + receive. The transfer happens through the passed file descriptor + (typically created with the pipe system call). The source client writes + the data in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + closes its end, at which point the transfer is complete. + </description> + <arg name="mime_type" type="string"/> + <arg name="fd" type="fd"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection offer"> + Destroy the primary selection offer. + </description> + </request> + + <event name="offer"> + <description summary="advertise offered mime type"> + Sent immediately after creating announcing the + wp_primary_selection_offer through + wp_primary_selection_device.data_offer. One event is sent per offered + mime type. + </description> + <arg name="mime_type" type="string"/> + </event> + </interface> + + <interface name="zwp_primary_selection_source_v1" version="1"> + <description summary="offer to replace the contents of the primary selection"> + The source side of a wp_primary_selection_offer, it provides a way to + describe the offered data and respond to requests to transfer the + requested contents of the primary selection clipboard. + </description> + + <request name="offer"> + <description summary="add an offered mime type"> + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + </description> + <arg name="mime_type" type="string"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection source"> + Destroy the primary selection source. + </description> + </request> + + <event name="send"> + <description summary="send the primary selection contents"> + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + </description> + <arg name="mime_type" type="string"/> + <arg name="fd" type="fd"/> + </event> + + <event name="cancelled"> + <description summary="request for primary selection contents was canceled"> + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + </description> + </event> + </interface> +</protocol> diff --git a/src/client/client.pro b/src/client/client.pro index db91bd691..4f4c58328 100644 --- a/src/client/client.pro +++ b/src/client/client.pro @@ -31,6 +31,7 @@ WAYLANDCLIENTSOURCES += \ ../extensions/touch-extension.xml \ ../extensions/qt-key-unstable-v1.xml \ ../extensions/qt-windowmanager.xml \ + ../3rdparty/protocol/wp-primary-selection-unstable-v1.xml \ ../3rdparty/protocol/text-input-unstable-v2.xml \ ../3rdparty/protocol/xdg-output-unstable-v1.xml \ ../3rdparty/protocol/wayland.xml @@ -46,6 +47,7 @@ SOURCES += qwaylandintegration.cpp \ qwaylandshellsurface.cpp \ qwaylandextendedsurface.cpp \ qwaylandsubsurface.cpp \ + qwaylandsurface.cpp \ qwaylandtouch.cpp \ qwaylandqtkey.cpp \ ../shared/qwaylandmimehelper.cpp \ @@ -70,6 +72,7 @@ HEADERS += qwaylandintegration_p.h \ qwaylandshellsurface_p.h \ qwaylandextendedsurface_p.h \ qwaylandsubsurface_p.h \ + qwaylandsurface_p.h \ qwaylandtouch_p.h \ qwaylandqtkey_p.h \ qwaylandabstractdecoration_p.h \ @@ -116,6 +119,11 @@ qtConfig(wayland-datadevice) { qwaylanddatasource.cpp } +qtConfig(wayland-client-primary-selection) { + HEADERS += qwaylandprimaryselectionv1_p.h + SOURCES += qwaylandprimaryselectionv1.cpp +} + qtConfig(draganddrop) { HEADERS += \ qwaylanddnd_p.h diff --git a/src/client/configure.json b/src/client/configure.json index 93c5d4e49..403a2edcf 100644 --- a/src/client/configure.json +++ b/src/client/configure.json @@ -75,6 +75,11 @@ "type": "compile", "test": "dmabuf_server_buffer", "use": "egl" + }, + "vulkan-server-buffer": { + "label": "Vulkan Buffer Sharing", + "type": "compile", + "test": "vulkan_server_buffer" } }, @@ -88,6 +93,11 @@ "condition": "features.draganddrop || features.clipboard", "output": [ "privateFeature" ] }, + "wayland-client-primary-selection": { + "label": "primary-selection clipboard", + "condition": "features.clipboard", + "output": [ "privateFeature" ] + }, "wayland-client-fullscreen-shell-v1": { "label": "fullscreen-shell-v1", "condition": "features.wayland-client", @@ -153,6 +163,11 @@ "condition": "features.wayland-client && features.opengl && features.egl && tests.dmabuf-server-buffer", "output": [ "privateFeature" ] }, + "wayland-vulkan-server-buffer": { + "label": "Vulkan-based server buffer integration", + "condition": "features.wayland-client && features.opengl && features.egl && tests.vulkan-server-buffer", + "output": [ "privateFeature" ] + }, "wayland-shm-emulation-server-buffer": { "label": "Shm emulation server buffer integration", "condition": "features.wayland-client && features.opengl", @@ -179,6 +194,8 @@ "xcomposite-glx", "wayland-drm-egl-server-buffer", "wayland-libhybris-egl-server-buffer", + "wayland-dmabuf-server-buffer", + "wayland-vulkan-server-buffer", "wayland-shm-emulation-server-buffer" ] }, diff --git a/src/client/hardwareintegration/qwaylandserverbufferintegration_p.h b/src/client/hardwareintegration/qwaylandserverbufferintegration_p.h index 632429bef..6833efd0c 100644 --- a/src/client/hardwareintegration/qwaylandserverbufferintegration_p.h +++ b/src/client/hardwareintegration/qwaylandserverbufferintegration_p.h @@ -70,7 +70,8 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandServerBuffer public: enum Format { RGBA32, - A8 + A8, + Custom }; QWaylandServerBuffer(); diff --git a/src/client/qwaylandclipboard.cpp b/src/client/qwaylandclipboard.cpp index 60820da92..369c6ec07 100644 --- a/src/client/qwaylandclipboard.cpp +++ b/src/client/qwaylandclipboard.cpp @@ -43,6 +43,9 @@ #include "qwaylanddataoffer_p.h" #include "qwaylanddatasource_p.h" #include "qwaylanddatadevice_p.h" +#if QT_CONFIG(wayland_client_primary_selection) +#include "qwaylandprimaryselectionv1_p.h" +#endif QT_BEGIN_NAMESPACE @@ -59,44 +62,74 @@ QWaylandClipboard::~QWaylandClipboard() QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) { - if (mode != QClipboard::Clipboard) + auto *seat = mDisplay->currentInputDevice(); + if (!seat) return &m_emptyData; - QWaylandInputDevice *inputDevice = mDisplay->currentInputDevice(); - if (!inputDevice || !inputDevice->dataDevice()) + switch (mode) { + case QClipboard::Clipboard: + if (auto *dataDevice = seat->dataDevice()) { + if (auto *source = dataDevice->selectionSource()) + return source->mimeData(); + if (auto *offer = dataDevice->selectionOffer()) + return offer->mimeData(); + } + return &m_emptyData; + case QClipboard::Selection: +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *selectionDevice = seat->primarySelectionDevice()) { + if (auto *source = selectionDevice->selectionSource()) + return source->mimeData(); + if (auto *offer = selectionDevice->selectionOffer()) + return offer->mimeData(); + } +#endif + return &m_emptyData; + default: return &m_emptyData; - - QWaylandDataSource *source = inputDevice->dataDevice()->selectionSource(); - if (source) { - return source->mimeData(); } - - if (inputDevice->dataDevice()->selectionOffer()) - return inputDevice->dataDevice()->selectionOffer()->mimeData(); - - return &m_emptyData; } void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) { - if (mode != QClipboard::Clipboard) - return; - - QWaylandInputDevice *inputDevice = mDisplay->currentInputDevice(); - if (!inputDevice || !inputDevice->dataDevice()) + auto *seat = mDisplay->currentInputDevice(); + if (!seat) return; static const QString plain = QStringLiteral("text/plain"); static const QString utf8 = QStringLiteral("text/plain;charset=utf-8"); + if (data && data->hasFormat(plain) && !data->hasFormat(utf8)) data->setData(utf8, data->data(plain)); - inputDevice->dataDevice()->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), data) : nullptr); - emitChanged(mode); + switch (mode) { + case QClipboard::Clipboard: + if (auto *dataDevice = seat->dataDevice()) { + dataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), data) : nullptr); + emitChanged(mode); + } + break; + case QClipboard::Selection: +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *selectionDevice = seat->primarySelectionDevice()) { + selectionDevice->setSelectionSource(data ? new QWaylandPrimarySelectionSourceV1(mDisplay->primarySelectionManager(), data) : nullptr); + emitChanged(mode); + } +#endif + break; + default: + break; + } } bool QWaylandClipboard::supportsMode(QClipboard::Mode mode) const { +#if QT_CONFIG(wayland_client_primary_selection) + if (mode == QClipboard::Selection) { + auto *seat = mDisplay->currentInputDevice(); + return seat && seat->primarySelectionDevice(); + } +#endif return mode == QClipboard::Clipboard; } diff --git a/src/client/qwaylandcursor.cpp b/src/client/qwaylandcursor.cpp index 8b2ed036d..165df7762 100644 --- a/src/client/qwaylandcursor.cpp +++ b/src/client/qwaylandcursor.cpp @@ -209,7 +209,7 @@ wl_cursor *QWaylandCursorTheme::requestCursor(WaylandCursor shape) return nullptr; } -struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape) +::wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation) { struct wl_cursor *waylandCursor = nullptr; @@ -227,8 +227,9 @@ struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape) return nullptr; } - struct wl_cursor_image *image = waylandCursor->images[0]; - struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); + int frame = wl_cursor_frame(waylandCursor, millisecondsIntoAnimation); + ::wl_cursor_image *image = waylandCursor->images[frame]; + ::wl_buffer *buffer = wl_cursor_image_get_buffer(image); if (!buffer) { qCWarning(lcQpaWayland) << "Could not find buffer for cursor"; return nullptr; diff --git a/src/client/qwaylandcursor_p.h b/src/client/qwaylandcursor_p.h index 6c48fb628..a4605f3d2 100644 --- a/src/client/qwaylandcursor_p.h +++ b/src/client/qwaylandcursor_p.h @@ -75,7 +75,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme public: static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName); ~QWaylandCursorTheme(); - struct wl_cursor_image *cursorImage(Qt::CursorShape shape); + ::wl_cursor_image *cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation = 0); private: enum WaylandCursor { diff --git a/src/client/qwaylanddatadevice.cpp b/src/client/qwaylanddatadevice.cpp index 300c9de0a..9dbef8d5b 100644 --- a/src/client/qwaylanddatadevice.cpp +++ b/src/client/qwaylanddatadevice.cpp @@ -47,6 +47,7 @@ #include "qwaylandinputdevice_p.h" #include "qwaylanddisplay_p.h" #include "qwaylandabstractdecoration_p.h" +#include "qwaylandsurface_p.h" #include <QtCore/QMimeData> #include <QtGui/QGuiApplication> @@ -107,11 +108,12 @@ void QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData)); connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); - QWaylandWindow *origin = m_display->currentInputDevice()->pointerFocus(); + auto *seat = m_display->currentInputDevice(); + auto *origin = seat->pointerFocus(); if (!origin) - origin = m_display->currentInputDevice()->touchFocus(); + origin = seat->touchFocus(); - start_drag(m_dragSource->object(), origin->object(), icon->object(), m_display->currentInputDevice()->serial()); + start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial()); } void QWaylandDataDevice::cancelDrag() @@ -151,9 +153,13 @@ void QWaylandDataDevice::data_device_drop() void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id) { - m_enterSerial = serial; - m_dragWindow = QWaylandWindow::fromWlSurface(surface)->window(); + auto *dragWaylandWindow = QWaylandWindow::fromWlSurface(surface); + if (!dragWaylandWindow) + return; // Ignore foreign surfaces + + m_dragWindow = dragWaylandWindow->window(); m_dragPoint = calculateDragPosition(x, y, m_dragWindow); + m_enterSerial = serial; QMimeData *dragData = nullptr; Qt::DropActions supportedActions; diff --git a/src/client/qwaylanddataoffer.cpp b/src/client/qwaylanddataoffer.cpp index 0c732c020..e31e1220f 100644 --- a/src/client/qwaylanddataoffer.cpp +++ b/src/client/qwaylanddataoffer.cpp @@ -58,7 +58,8 @@ static QString utf8Text() QWaylandDataOffer::QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer) : QtWayland::wl_data_offer(offer) - , m_mimeData(new QWaylandMimeData(this, display)) + , m_display(display) + , m_mimeData(new QWaylandMimeData(this)) { } @@ -81,14 +82,19 @@ QMimeData *QWaylandDataOffer::mimeData() return m_mimeData.data(); } +void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd) +{ + receive(mimeType, fd); + wl_display_flush(m_display->wl_display()); +} + void QWaylandDataOffer::data_offer_offer(const QString &mime_type) { m_mimeData->appendFormat(mime_type); } -QWaylandMimeData::QWaylandMimeData(QWaylandDataOffer *dataOffer, QWaylandDisplay *display) +QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer) : m_dataOffer(dataOffer) - , m_display(display) { } @@ -140,8 +146,7 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::T return QVariant(); } - m_dataOffer->receive(mime, pipefd[1]); - wl_display_flush(m_display->wl_display()); + m_dataOffer->startReceiving(mime, pipefd[1]); close(pipefd[1]); diff --git a/src/client/qwaylanddataoffer_p.h b/src/client/qwaylanddataoffer_p.h index 5412400a5..9cf1483ca 100644 --- a/src/client/qwaylanddataoffer_p.h +++ b/src/client/qwaylanddataoffer_p.h @@ -65,27 +65,40 @@ namespace QtWaylandClient { class QWaylandDisplay; class QWaylandMimeData; -class Q_WAYLAND_CLIENT_EXPORT QWaylandDataOffer : public QtWayland::wl_data_offer +class QWaylandAbstractDataOffer +{ +public: + virtual void startReceiving(const QString &mimeType, int fd) = 0; + virtual QMimeData *mimeData() = 0; + + virtual ~QWaylandAbstractDataOffer() = default; +}; + +class Q_WAYLAND_CLIENT_EXPORT QWaylandDataOffer + : public QtWayland::wl_data_offer // needs to be the first because we do static casts from the user pointer to the wrapper + , public QWaylandAbstractDataOffer { public: explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer); ~QWaylandDataOffer() override; + QMimeData *mimeData() override; QString firstFormat() const; - QMimeData *mimeData(); + void startReceiving(const QString &mimeType, int fd) override; protected: void data_offer_offer(const QString &mime_type) override; private: + QWaylandDisplay *m_display = nullptr; QScopedPointer<QWaylandMimeData> m_mimeData; }; class QWaylandMimeData : public QInternalMimeData { public: - explicit QWaylandMimeData(QWaylandDataOffer *dataOffer, QWaylandDisplay *display); + explicit QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer); ~QWaylandMimeData() override; void appendFormat(const QString &mimeType); @@ -98,13 +111,12 @@ protected: private: int readData(int fd, QByteArray &data) const; - mutable QWaylandDataOffer *m_dataOffer = nullptr; - QWaylandDisplay *m_display = nullptr; + QWaylandAbstractDataOffer *m_dataOffer = nullptr; mutable QStringList m_types; mutable QHash<QString, QByteArray> m_data; }; -} +} // namespace QtWaylandClient QT_END_NAMESPACE #endif diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 4a91c1c96..d0f981702 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -41,6 +41,7 @@ #include "qwaylandintegration_p.h" #include "qwaylandwindow_p.h" +#include "qwaylandsurface_p.h" #include "qwaylandabstractdecoration_p.h" #include "qwaylandscreen_p.h" #include "qwaylandcursor_p.h" @@ -51,7 +52,10 @@ #if QT_CONFIG(wayland_datadevice) #include "qwaylanddatadevicemanager_p.h" #include "qwaylanddatadevice_p.h" -#endif +#endif // QT_CONFIG(wayland_datadevice) +#if QT_CONFIG(wayland_client_primary_selection) +#include "qwaylandprimaryselectionv1_p.h" +#endif // QT_CONFIG(wayland_client_primary_selection) #if QT_CONFIG(cursor) #include <wayland-cursor.h> #endif @@ -68,6 +72,7 @@ #include "qwaylandqtkey_p.h" #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> +#include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h> #include <QtCore/private/qcore_unix_p.h> @@ -109,7 +114,7 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) return nullptr; } - return mSubCompositor->get_subsurface(window->object(), parent->object()); + return mSubCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); } QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const @@ -317,6 +322,10 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin mTouchExtension.reset(new QWaylandTouchExtension(this, id)); } else if (interface == QStringLiteral("zqt_key_v1")) { mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id)); +#if QT_CONFIG(wayland_client_primary_selection) + } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { + mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); +#endif } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) @@ -377,9 +386,9 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } -bool QWaylandDisplay::hasRegistryGlobal(const QString &interfaceName) +bool QWaylandDisplay::hasRegistryGlobal(QStringView interfaceName) const { - Q_FOREACH (const RegistryGlobal &global, mGlobals) + for (const RegistryGlobal &global : mGlobals) if (global.interface == interfaceName) return true; diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index 558d8d9b5..1f085e67e 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -93,11 +93,15 @@ class QWaylandScreen; class QWaylandClientBufferIntegration; class QWaylandWindowManagerIntegration; class QWaylandDataDeviceManager; +#if QT_CONFIG(wayland_client_primary_selection) +class QWaylandPrimarySelectionDeviceManagerV1; +#endif class QWaylandTouchExtension; class QWaylandQtKeyExtension; class QWaylandWindow; class QWaylandIntegration; class QWaylandHardwareIntegration; +class QWaylandSurface; class QWaylandShellIntegration; class QWaylandCursor; class QWaylandCursorTheme; @@ -149,6 +153,9 @@ public: #if QT_CONFIG(wayland_datadevice) QWaylandDataDeviceManager *dndSelectionHandler() const { return mDndSelectionHandler.data(); } #endif +#if QT_CONFIG(wayland_client_primary_selection) + QWaylandPrimarySelectionDeviceManagerV1 *primarySelectionManager() const { return mPrimarySelectionManager.data(); } +#endif QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); } QWaylandTouchExtension *touchExtension() const { return mTouchExtension.data(); } QtWayland::zwp_text_input_manager_v2 *textInputManager() const { return mTextInputManager.data(); } @@ -166,7 +173,7 @@ public: : id(id_), interface(interface_), version(version_), registry(registry_) { } }; QList<RegistryGlobal> globals() const { return mGlobals; } - bool hasRegistryGlobal(const QString &interfaceName); + bool hasRegistryGlobal(QStringView interfaceName) const; /* wl_registry_add_listener does not add but rather sets a listener, so this function is used * to enable many listeners at once. */ @@ -237,6 +244,9 @@ private: QScopedPointer<QWaylandTouchExtension> mTouchExtension; QScopedPointer<QWaylandQtKeyExtension> mQtKeyExtension; QScopedPointer<QWaylandWindowManagerIntegration> mWindowManagerIntegration; +#if QT_CONFIG(wayland_client_primary_selection) + QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager; +#endif QScopedPointer<QtWayland::zwp_text_input_manager_v2> mTextInputManager; QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration; QScopedPointer<QtWayland::zxdg_output_manager_v1> mXdgOutputManager; diff --git a/src/client/qwaylandextendedsurface.cpp b/src/client/qwaylandextendedsurface.cpp index c5db6d7ba..a7836e292 100644 --- a/src/client/qwaylandextendedsurface.cpp +++ b/src/client/qwaylandextendedsurface.cpp @@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { QWaylandExtendedSurface::QWaylandExtendedSurface(QWaylandWindow *window) - : QtWayland::qt_extended_surface(window->display()->windowExtension()->get_extended_surface(window->object())) + : QtWayland::qt_extended_surface(window->display()->windowExtension()->get_extended_surface(window->wlSurface())) , m_window(window) { } diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index c6f287dda..1d34f06cc 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -119,7 +119,7 @@ void QWaylandTextInput::updateState(Qt::InputMethodQueries queries, uint32_t fla if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) return; - struct ::wl_surface *surface = static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle())->object(); + auto *surface = static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle())->wlSurface(); if (!surface || (surface != m_surface)) return; @@ -428,7 +428,7 @@ static ::wl_surface *surfaceForWindow(QWindow *window) return nullptr; auto *waylandWindow = static_cast<QWaylandWindow *>(window->handle()); - return waylandWindow->wl_surface::object(); + return waylandWindow->wlSurface(); } void QWaylandInputContext::update(Qt::InputMethodQueries queries) @@ -534,7 +534,7 @@ void QWaylandInputContext::setFocusObject(QObject *) if (mCurrentWindow && mCurrentWindow->handle()) { if (mCurrentWindow.data() != window || !inputMethodAccepted()) { - struct ::wl_surface *surface = static_cast<QWaylandWindow *>(mCurrentWindow->handle())->object(); + auto *surface = static_cast<QWaylandWindow *>(mCurrentWindow->handle())->wlSurface(); if (surface) textInput()->disable(surface); mCurrentWindow.clear(); @@ -543,7 +543,7 @@ void QWaylandInputContext::setFocusObject(QObject *) if (window && window->handle() && inputMethodAccepted()) { if (mCurrentWindow.data() != window) { - struct ::wl_surface *surface = static_cast<QWaylandWindow *>(window->handle())->object(); + auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface(); if (surface) { textInput()->enable(surface); mCurrentWindow = window; diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 39c02d962..8a580898d 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -41,11 +41,15 @@ #include "qwaylandintegration_p.h" #include "qwaylandwindow_p.h" +#include "qwaylandsurface_p.h" #include "qwaylandbuffer_p.h" #if QT_CONFIG(wayland_datadevice) #include "qwaylanddatadevice_p.h" #include "qwaylanddatadevicemanager_p.h" #endif +#if QT_CONFIG(wayland_client_primary_selection) +#include "qwaylandprimaryselectionv1_p.h" +#endif #include "qwaylandtouch_p.h" #include "qwaylandscreen_p.h" #include "qwaylandcursor_p.h" @@ -73,6 +77,8 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { +Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); + QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) : mParent(p) { @@ -147,25 +153,45 @@ QWaylandInputDevice::Pointer::~Pointer() wl_pointer_destroy(object()); } +QWaylandWindow *QWaylandInputDevice::Pointer::focusWindow() const +{ + return mFocus ? mFocus->waylandWindow() : nullptr; +} + #if QT_CONFIG(cursor) -class CursorSurface : public QObject, public QtWayland::wl_surface +class WlCallback : public QtWayland::wl_callback { +public: + explicit WlCallback(::wl_callback *callback, std::function<void(uint32_t)> fn, bool autoDelete = false) + : QtWayland::wl_callback(callback) + , m_fn(fn) + , m_autoDelete(autoDelete) + {} + ~WlCallback() override { wl_callback_destroy(object()); } + bool done() const { return m_done; } + void callback_done(uint32_t callback_data) override { + m_done = true; + m_fn(callback_data); + if (m_autoDelete) + delete this; + } +private: + bool m_done = false; + std::function<void(uint32_t)> m_fn; + bool m_autoDelete = false; +}; + +class CursorSurface : public QWaylandSurface { public: explicit CursorSurface(QWaylandInputDevice::Pointer *pointer, QWaylandDisplay *display) - : m_pointer(pointer) + : QWaylandSurface(display) + , m_pointer(pointer) { - init(display->createSurface(this)); //TODO: When we upgrade to libwayland 1.10, use wl_surface_get_version instead. m_version = display->compositorVersion(); - connect(qApp, &QGuiApplication::screenRemoved, this, [this](QScreen *screen) { - int oldScale = outputScale(); - if (!m_screens.removeOne(static_cast<QWaylandScreen *>(screen->handle()))) - return; - - if (outputScale() != oldScale) - m_pointer->updateCursor(); - }); + connect(this, &QWaylandSurface::screensChanged, + m_pointer, &QWaylandInputDevice::Pointer::updateCursor); } void hide() @@ -177,7 +203,7 @@ public: } // Size and hotspot are in surface coordinates - void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale) + void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale, bool animated = false) { // Calling code needs to ensure buffer scale is supported if != 1 Q_ASSERT(bufferScale == 1 || m_version >= 3); @@ -194,6 +220,13 @@ public: attach(buffer, 0, 0); damage(0, 0, size.width(), size.height()); + m_frameCallback.reset(); + if (animated) { + m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time){ + Q_UNUSED(time); + m_pointer->updateCursor(); + })); + } commit(); } @@ -205,38 +238,12 @@ public: return scale; } -protected: - void surface_enter(struct ::wl_output *output) override - { - int oldScale = outputScale(); - auto *screen = QWaylandScreen::fromWlOutput(output); - if (m_screens.contains(screen)) - return; - - m_screens.append(screen); - - if (outputScale() != oldScale) - m_pointer->updateCursor(); - } - - void surface_leave(struct ::wl_output *output) override - { - int oldScale = outputScale(); - auto *screen = QWaylandScreen::fromWlOutput(output); - - if (!m_screens.removeOne(screen)) - return; - - if (outputScale() != oldScale) - m_pointer->updateCursor(); - } - private: + QScopedPointer<WlCallback> m_frameCallback; QWaylandInputDevice::Pointer *m_pointer = nullptr; uint m_version = 0; uint m_setSerial = 0; QPoint m_hotspot; - QVector<QWaylandScreen *> m_screens; }; QString QWaylandInputDevice::Pointer::cursorThemeName() const @@ -311,12 +318,14 @@ void QWaylandInputDevice::Pointer::updateCursor() updateCursorTheme(); // Set from shape using theme - if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape)) { + uint time = seat()->mCursor.animationTimer.elapsed(); + if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape, time)) { struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); int bufferScale = mCursor.themeBufferScale; QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; QSize size = QSize(image->width, image->height) / bufferScale; - getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale); + bool animated = image->delay > 0; + getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated); return; } @@ -346,10 +355,10 @@ QWaylandInputDevice::Touch::~Touch() } QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id) - : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 4)) + : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 5)) , mQDisplay(display) , mDisplay(display->wl_display()) - , mVersion(qMin(version, 4)) + , mVersion(qMin(version, 5)) { #if QT_CONFIG(wayland_datadevice) if (mQDisplay->dndSelectionHandler()) { @@ -357,6 +366,12 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, } #endif +#if QT_CONFIG(wayland_client_primary_selection) + // TODO: Could probably decouple this more if there was a signal for new seat added + if (auto *psm = mQDisplay->primarySelectionManager()) + setPrimarySelectionDevice(psm->createDevice(this)); +#endif + if (mQDisplay->textInputManager()) mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); @@ -440,6 +455,18 @@ QWaylandDataDevice *QWaylandInputDevice::dataDevice() const } #endif +#if QT_CONFIG(wayland_client_primary_selection) +void QWaylandInputDevice::setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice) +{ + mPrimarySelectionDevice.reset(primarySelectionDevice); +} + +QWaylandPrimarySelectionDeviceV1 *QWaylandInputDevice::primarySelectionDevice() const +{ + return mPrimarySelectionDevice.data(); +} +#endif + void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput) { mTextInput.reset(textInput); @@ -458,7 +485,7 @@ void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) QWaylandWindow *QWaylandInputDevice::pointerFocus() const { - return mPointer ? mPointer->mFocus : nullptr; + return mPointer ? mPointer->focusWindow() : nullptr; } QWaylandWindow *QWaylandInputDevice::keyboardFocus() const @@ -517,6 +544,7 @@ void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer< mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor; mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint(); mCursor.fallbackOutputScale = fallbackOutputScale; + mCursor.animationTimer.start(); if (mCursor.shape == Qt::BitmapCursor) { mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor); @@ -543,8 +571,9 @@ void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer< class EnterEvent : public QWaylandPointerEvent { public: - EnterEvent(const QPointF &l, const QPointF &g) - : QWaylandPointerEvent(QWaylandPointerEvent::Enter, 0, l, g, nullptr, Qt::NoModifier) + EnterEvent(QWaylandWindow *surface, const QPointF &local, const QPointF &global) + : QWaylandPointerEvent(QWaylandPointerEvent::Enter, Qt::NoScrollPhase, surface, 0, + local, global, nullptr, Qt::NoModifier) {} }; @@ -556,14 +585,17 @@ void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surf QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; // Ignore foreign surfaces + if (mFocus) { qCWarning(lcQpaWayland) << "The compositor sent a wl_pointer.enter event before sending a" << "leave event first, this is not allowed by the wayland protocol" << "attempting to work around it by invalidating the current focus"; invalidateFocus(); } - mFocus = window; - connect(mFocus, &QWaylandWindow::wlSurfaceDestroyed, this, &Pointer::handleFocusDestroyed); + mFocus = window->waylandSurface(); + connect(mFocus, &QObject::destroyed, this, &Pointer::handleFocusDestroyed); mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy)); mGlobalPos = window->window()->mapToGlobal(mSurfacePos.toPoint()); @@ -577,12 +609,19 @@ void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surf #endif QWaylandWindow *grab = QWaylandWindow::mouseGrab(); - if (!grab) { - EnterEvent evt(mSurfacePos, mGlobalPos); - window->handleMouse(mParent, evt); - } + if (!grab) + setFrameEvent(new EnterEvent(window, mSurfacePos, mGlobalPos)); } +class LeaveEvent : public QWaylandPointerEvent +{ +public: + LeaveEvent(QWaylandWindow *surface, const QPointF &localPos, const QPointF &globalPos) + : QWaylandPointerEvent(QWaylandPointerEvent::Leave, Qt::NoScrollPhase, surface, 0, + localPos, globalPos, nullptr, Qt::NoModifier) + {} +}; + void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surface *surface) { // The event may arrive after destroying the window, indicated by @@ -590,10 +629,12 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac if (!surface) return; - if (!QWaylandWindow::mouseGrab()) { - QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); - window->handleMouseLeave(mParent); - } + auto *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; // Ignore foreign surfaces + + if (!QWaylandWindow::mouseGrab()) + setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos)); invalidateFocus(); mButtons = Qt::NoButton; @@ -604,15 +645,17 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac class MotionEvent : public QWaylandPointerEvent { public: - MotionEvent(ulong t, const QPointF &l, const QPointF &g, Qt::MouseButtons b, Qt::KeyboardModifiers m) - : QWaylandPointerEvent(QWaylandPointerEvent::Motion, t, l, g, b, m) + MotionEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, + const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QWaylandPointerEvent::Motion, Qt::NoScrollPhase, surface, + timestamp, localPos, globalPos, buttons, modifiers) { } }; void QWaylandInputDevice::Pointer::pointer_motion(uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { - QWaylandWindow *window = mFocus; + QWaylandWindow *window = focusWindow(); if (!window) { // We destroyed the pointer focus surface, but the server didn't get the message yet... // or the server didn't send an enter event first. In either case, ignore the event. @@ -634,18 +677,37 @@ void QWaylandInputDevice::Pointer::pointer_motion(uint32_t time, wl_fixed_t surf // so we just set it outside of the window boundaries. pos = QPointF(-1, -1); global = grab->window()->mapToGlobal(pos.toPoint()); - MotionEvent e(time, pos, global, mButtons, mParent->modifiers()); - grab->handleMouse(mParent, e); - } else { - MotionEvent e(time, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers()); - window->handleMouse(mParent, e); + window = grab; } + setFrameEvent(new MotionEvent(window, time, pos, global, mButtons, mParent->modifiers())); } +class PressEvent : public QWaylandPointerEvent +{ +public: + PressEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, + const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QWaylandPointerEvent::Press, Qt::NoScrollPhase, surface, + timestamp, localPos, globalPos, buttons, modifiers) + { + } +}; + +class ReleaseEvent : public QWaylandPointerEvent +{ +public: + ReleaseEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, + const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QWaylandPointerEvent::Release, Qt::NoScrollPhase, surface, + timestamp, localPos, globalPos, buttons, modifiers) + { + } +}; + void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - QWaylandWindow *window = mFocus; + QWaylandWindow *window = focusWindow(); if (!window) { // We destroyed the pointer focus surface, but the server didn't get the message yet... // or the server didn't send an enter event first. In either case, ignore the event. @@ -687,20 +749,25 @@ void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time mParent->mQDisplay->setLastInputDevice(mParent, serial, window); QWaylandWindow *grab = QWaylandWindow::mouseGrab(); - if (grab && grab != mFocus) { - QPointF pos = QPointF(-1, -1); - QPointF global = grab->window()->mapToGlobal(pos.toPoint()); - MotionEvent e(time, pos, global, mButtons, mParent->modifiers()); - grab->handleMouse(mParent, e); - } else if (window) { - MotionEvent e(time, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers()); - window->handleMouse(mParent, e); + + QPointF pos = mSurfacePos; + QPointF global = mGlobalPos; + if (grab && grab != focusWindow()) { + pos = QPointF(-1, -1); + global = grab->window()->mapToGlobal(pos.toPoint()); + + window = grab; } + + if (state) + setFrameEvent(new PressEvent(window, time, pos, global, mButtons, mParent->modifiers())); + else + setFrameEvent(new ReleaseEvent(window, time, pos, global, mButtons, mParent->modifiers())); } void QWaylandInputDevice::Pointer::invalidateFocus() { - disconnect(mFocus, &QWaylandWindow::wlSurfaceDestroyed, this, &Pointer::handleFocusDestroyed); + disconnect(mFocus, &QObject::destroyed, this, &Pointer::handleFocusDestroyed); mFocus = nullptr; mEnterSerial = 0; } @@ -708,45 +775,265 @@ void QWaylandInputDevice::Pointer::invalidateFocus() void QWaylandInputDevice::Pointer::releaseButtons() { mButtons = Qt::NoButton; - MotionEvent e(mParent->mTime, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers()); - if (mFocus) - mFocus->handleMouse(mParent, e); + + if (auto *window = focusWindow()) { + MotionEvent e(focusWindow(), mParent->mTime, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers()); + window->handleMouse(mParent, e); + } } class WheelEvent : public QWaylandPointerEvent { public: - WheelEvent(ulong t, const QPointF &l, const QPointF &g, const QPoint &pd, const QPoint &ad, Qt::KeyboardModifiers m) - : QWaylandPointerEvent(QWaylandPointerEvent::Wheel, t, l, g, pd, ad, m) + WheelEvent(QWaylandWindow *surface, Qt::ScrollPhase phase, ulong timestamp, const QPointF &local, + const QPointF &global, const QPoint &pixelDelta, const QPoint &angleDelta, + Qt::MouseEventSource source, Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QWaylandPointerEvent::Wheel, phase, surface, timestamp, + local, global, pixelDelta, angleDelta, source, modifiers) { } }; void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, int32_t value) { - QWaylandWindow *window = mFocus; - if (!window) { + if (!focusWindow()) { // We destroyed the pointer focus surface, but the server didn't get the message yet... // or the server didn't send an enter event first. In either case, ignore the event. return; } - QPoint pixelDelta; - QPoint angleDelta; + // Get the delta and convert it into the expected range + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + mFrameData.delta.ry() += wl_fixed_to_double(value); + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis vertical:" << mFrameData.delta.y(); + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + mFrameData.delta.rx() += wl_fixed_to_double(value); + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis horizontal:" << mFrameData.delta.x(); + break; + default: + //TODO: is this really needed? + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis: Unknown axis:" << axis; + return; + } - //normalize value and inverse axis - int valueDelta = wl_fixed_to_int(value) * -12; + mParent->mTime = time; - if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { - pixelDelta = QPoint(); - angleDelta.setX(valueDelta); - } else { - pixelDelta = QPoint(); - angleDelta.setY(valueDelta); + if (mParent->mVersion < WL_POINTER_FRAME_SINCE_VERSION) { + qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; + flushFrameEvent(); + } +} + +void QWaylandInputDevice::Pointer::pointer_frame() +{ + flushFrameEvent(); +} + +void QWaylandInputDevice::Pointer::pointer_axis_source(uint32_t source) +{ + switch (source) { + case axis_source_wheel: + qCDebug(lcQpaWaylandInput) << "Axis source wheel"; + break; + case axis_source_finger: + qCDebug(lcQpaWaylandInput) << "Axis source finger"; + break; + case axis_source_continuous: + qCDebug(lcQpaWaylandInput) << "Axis source continuous"; + break; + } + mFrameData.axisSource = axis_source(source); +} + +void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axis) +{ + if (!focusWindow()) + return; + + mParent->mTime = time; + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop"; + mFrameData.delta.setY(0); //TODO: what's the point of doing this? + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop"; + mFrameData.delta.setX(0); + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis + << "This is most likely a compositor bug"; + return; + } + + // May receive axis_stop for events we haven't sent a ScrollBegin for because + // most axis_sources do not mandate an axis_stop event to be sent. + if (!mScrollBeginSent) { + // TODO: For now, we just ignore these events, but we could perhaps take this as an + // indication that this compositor will in fact send axis_stop events for these sources + // and send a ScrollBegin the next time an axis_source event with this type is encountered. + return; + } + + QWaylandWindow *target = QWaylandWindow::mouseGrab(); + if (!target) + target = focusWindow(); + Qt::KeyboardModifiers mods = mParent->modifiers(); + WheelEvent wheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, + QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods); + target->handleMouse(mParent, wheelEvent); + mScrollBeginSent = false; + mScrollDeltaRemainder = QPointF(); +} + +void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value) +{ + if (!focusWindow()) + return; + + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value; + mFrameData.discreteDelta.ry() += value; + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value; + mFrameData.discreteDelta.rx() += value; + break; + default: + //TODO: is this really needed? + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_discrete: Unknown axis:" << axis; + return; + } +} + +void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) +{ + qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; + if (mFrameData.event && mFrameData.event->type != event->type) { + qCDebug(lcQpaWaylandInput) << "Flushing; previous was " << mFrameData.event->type; + flushFrameEvent(); + } + + mFrameData.event = event; + + if (mParent->mVersion < WL_POINTER_FRAME_SINCE_VERSION) { + qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; + flushFrameEvent(); + } +} + +void QWaylandInputDevice::Pointer::FrameData::resetScrollData() +{ + discreteDelta = QPoint(); + delta = QPointF(); + axisSource = axis_source_wheel; +} + +bool QWaylandInputDevice::Pointer::FrameData::hasPixelDelta() const +{ + switch (axisSource) { + case axis_source_wheel_tilt: // sideways tilt of the wheel + case axis_source_wheel: + // In the case of wheel events, a pixel delta doesn't really make sense, + // and will make Qt think this is a continuous scroll event when it isn't, + // so just ignore it. + return false; + case axis_source_finger: + case axis_source_continuous: + return !delta.isNull(); + } +} + +QPoint QWaylandInputDevice::Pointer::FrameData::pixelDeltaAndError(QPointF *accumulatedError) const +{ + if (!hasPixelDelta()) + return QPoint(); + + Q_ASSERT(accumulatedError); + // Add accumulated rounding error before rounding again + QPoint pixelDelta = (delta + *accumulatedError).toPoint(); + *accumulatedError += delta - pixelDelta; + Q_ASSERT(qAbs(accumulatedError->x()) < 1.0); + Q_ASSERT(qAbs(accumulatedError->y()) < 1.0); + return pixelDelta; +} + +QPoint QWaylandInputDevice::Pointer::FrameData::angleDelta() const +{ + if (discreteDelta.isNull()) { + // If we didn't get any discrete events, then we need to fall back to + // the continuous information. + return (delta * -12).toPoint(); //TODO: why multiply by 12? + } + + // The angle delta is in eights of degrees, and our docs says most mice have + // 1 click = 15 degrees. It's also in the opposite direction of surface space. + return -discreteDelta * 15 * 8; +} + +Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const +{ + switch (axisSource) { + case axis_source_wheel_tilt: // sideways tilt of the wheel + case axis_source_wheel: + return Qt::MouseEventNotSynthesized; + case axis_source_finger: + case axis_source_continuous: + default: // Whatever other sources might be added are probably not mouse wheels + return Qt::MouseEventSynthesizedBySystem; } +} + +void QWaylandInputDevice::Pointer::flushScrollEvent() +{ + QPoint angleDelta = mFrameData.angleDelta(); + + // Angle delta is required for Qt wheel events, so don't try to send events if it's zero + if (!angleDelta.isNull()) { + QWaylandWindow *target = QWaylandWindow::mouseGrab(); + if (!target) + target = focusWindow(); + + if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) { + qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin"; + target->handleMouse(mParent, WheelEvent(focusWindow(), Qt::ScrollBegin, mParent->mTime, + mSurfacePos, mGlobalPos, QPoint(), QPoint(), + Qt::MouseEventNotSynthesized, + mParent->modifiers())); + mScrollBeginSent = true; + mScrollDeltaRemainder = QPointF(); + } + + Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase; + QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder); + Qt::MouseEventSource source = mFrameData.wheelEventSource(); + + qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta; + target->handleMouse(mParent, WheelEvent(focusWindow(), phase, mParent->mTime, mSurfacePos, mGlobalPos, + pixelDelta, angleDelta, source, mParent->modifiers())); + } + + mFrameData.resetScrollData(); +} - WheelEvent e(time, mSurfacePos, mGlobalPos, pixelDelta, angleDelta, mParent->modifiers()); - window->handleMouse(mParent, e); +void QWaylandInputDevice::Pointer::flushFrameEvent() +{ + if (mFrameData.event) { + mFrameData.event->surface->handleMouse(mParent, *mFrameData.event); + delete mFrameData.event; + mFrameData.event = nullptr; + } + + //TODO: do modifiers get passed correctly here? + flushScrollEvent(); +} + +bool QWaylandInputDevice::Pointer::isDefinitelyTerminated(QtWayland::wl_pointer::axis_source source) const +{ + return source == axis_source_finger; } void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd, uint32_t size) @@ -913,7 +1200,7 @@ void QWaylandInputDevice::Keyboard::handleFocusDestroyed() // surface, so we still need to disconnect the signal auto *window = qobject_cast<QWaylandWindow *>(sender()); disconnect(window, &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed); - Q_ASSERT(window->object() == mFocus); + Q_ASSERT(window->wlSurface() == mFocus); handleFocusLost(); } @@ -924,6 +1211,10 @@ void QWaylandInputDevice::Keyboard::handleFocusLost() if (auto *dataDevice = mParent->dataDevice()) dataDevice->invalidateSelectionOffer(); #endif +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *device = mParent->primarySelectionDevice()) + device->invalidateSelectionOffer(); +#endif mParent->mQDisplay->handleKeyboardFocusChanged(mParent); mRepeatTimer.stop(); } @@ -965,9 +1256,13 @@ void QWaylandInputDevice::Touch::touch_down(uint32_t serial, if (!surface) return; + auto *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; // Ignore foreign surfaces + mParent->mTime = time; mParent->mSerial = serial; - mFocus = QWaylandWindow::fromWlSurface(surface); + mFocus = window; mParent->mQDisplay->setLastInputDevice(mParent, serial, mFocus); mParent->handleTouchPoint(id, wl_fixed_to_double(x), wl_fixed_to_double(y), Qt::TouchPointPressed); } @@ -1026,7 +1321,7 @@ void QWaylandInputDevice::handleTouchPoint(int id, double x, double y, Qt::Touch //is it possible that mTouchFocus is null; if (!win && mPointer) - win = mPointer->mFocus; + win = mPointer->focusWindow(); if (!win && mKeyboard) win = mKeyboard->focusWindow(); if (!win || !win->window()) diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h index 39ca9dca5..06ba5d566 100644 --- a/src/client/qwaylandinputdevice_p.h +++ b/src/client/qwaylandinputdevice_p.h @@ -69,7 +69,8 @@ #endif #include <QtCore/QDebug> -#include <QPointer> +#include <QtCore/QElapsedTimer> +#include <QtCore/QPointer> #if QT_CONFIG(cursor) struct wl_cursor_image; @@ -77,11 +78,17 @@ struct wl_cursor_image; QT_BEGIN_NAMESPACE +namespace QtWayland { +class zwp_primary_selection_device_v1; +} //namespace QtWayland + namespace QtWaylandClient { -class QWaylandWindow; -class QWaylandDisplay; class QWaylandDataDevice; +class QWaylandDisplay; +#if QT_CONFIG(wayland_client_primary_selection) +class QWaylandPrimarySelectionDeviceV1; +#endif class QWaylandTextInput; #if QT_CONFIG(cursor) class QWaylandCursorTheme; @@ -115,6 +122,11 @@ public: QWaylandDataDevice *dataDevice() const; #endif +#if QT_CONFIG(wayland_client_primary_selection) + void setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice); + QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice() const; +#endif + void setTextInput(QWaylandTextInput *textInput); QWaylandTextInput *textInput() const; @@ -150,6 +162,7 @@ private: Qt::CursorShape shape = Qt::ArrowCursor; int fallbackOutputScale = 1; QPoint hotspot; + QElapsedTimer animationTimer; } mCursor; #endif @@ -157,6 +170,10 @@ private: QWaylandDataDevice *mDataDevice = nullptr; #endif +#if QT_CONFIG(wayland_client_primary_selection) + QScopedPointer<QWaylandPrimarySelectionDeviceV1> mPrimarySelectionDevice; +#endif + Keyboard *mKeyboard = nullptr; Pointer *mPointer = nullptr; Touch *mTouch = nullptr; @@ -256,6 +273,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Pointer : public QObject, pub public: explicit Pointer(QWaylandInputDevice *seat); ~Pointer() override; + QWaylandWindow *focusWindow() const; #if QT_CONFIG(cursor) QString cursorThemeName() const; int cursorSize() const; // in surface coordinates @@ -277,6 +295,10 @@ protected: void pointer_axis(uint32_t time, uint32_t axis, wl_fixed_t value) override; + void pointer_axis_source(uint32_t source) override; + void pointer_axis_stop(uint32_t time, uint32_t axis) override; + void pointer_axis_discrete(uint32_t axis, int32_t value) override; + void pointer_frame() override; private slots: void handleFocusDestroyed() { invalidateFocus(); } @@ -288,7 +310,7 @@ public: void releaseButtons(); QWaylandInputDevice *mParent = nullptr; - QPointer<QWaylandWindow> mFocus; + QPointer<QWaylandSurface> mFocus; uint32_t mEnterSerial = 0; #if QT_CONFIG(cursor) struct { @@ -304,6 +326,30 @@ public: wl_buffer *mCursorBuffer = nullptr; Qt::CursorShape mCursorShape = Qt::BitmapCursor; #endif + + struct FrameData { + QWaylandPointerEvent *event = nullptr; + + QPointF delta; + QPoint discreteDelta; + axis_source axisSource = axis_source_wheel; + + void resetScrollData(); + bool hasPixelDelta() const; + QPoint pixelDeltaAndError(QPointF *accumulatedError) const; + QPoint pixelDelta() const { return hasPixelDelta() ? delta.toPoint() : QPoint(); } + QPoint angleDelta() const; + Qt::MouseEventSource wheelEventSource() const; + } mFrameData; + + bool mScrollBeginSent = false; + QPointF mScrollDeltaRemainder; + + void setFrameEvent(QWaylandPointerEvent *event); + void flushScrollEvent(); + void flushFrameEvent(); +private: //TODO: should other methods be private as well? + bool isDefinitelyTerminated(axis_source source) const; }; class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Touch : public QtWayland::wl_touch @@ -339,38 +385,58 @@ public: class QWaylandPointerEvent { + Q_GADGET public: enum Type { Enter, + Leave, Motion, + Press, + Release, Wheel }; - inline QWaylandPointerEvent(Type t, ulong ts, const QPointF &l, const QPointF &g, Qt::MouseButtons b, Qt::KeyboardModifiers m) - : type(t) - , timestamp(ts) - , local(l) - , global(g) - , buttons(b) - , modifiers(m) + Q_ENUM(Type) + + inline QWaylandPointerEvent(Type type, Qt::ScrollPhase phase, QWaylandWindow *surface, + ulong timestamp, const QPointF &localPos, const QPointF &globalPos, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : type(type) + , phase(phase) + , timestamp(timestamp) + , local(localPos) + , global(globalPos) + , buttons(buttons) + , modifiers(modifiers) + , surface(surface) {} - inline QWaylandPointerEvent(Type t, ulong ts, const QPointF &l, const QPointF &g, const QPoint &pd, const QPoint &ad, Qt::KeyboardModifiers m) - : type(t) - , timestamp(ts) - , local(l) - , global(g) - , modifiers(m) - , pixelDelta(pd) - , angleDelta(ad) + inline QWaylandPointerEvent(Type type, Qt::ScrollPhase phase, QWaylandWindow *surface, + ulong timestamp, const QPointF &local, const QPointF &global, + const QPoint &pixelDelta, const QPoint &angleDelta, + Qt::MouseEventSource source, + Qt::KeyboardModifiers modifiers) + : type(type) + , phase(phase) + , timestamp(timestamp) + , local(local) + , global(global) + , modifiers(modifiers) + , pixelDelta(pixelDelta) + , angleDelta(angleDelta) + , source(source) + , surface(surface) {} Type type; - ulong timestamp; + Qt::ScrollPhase phase = Qt::NoScrollPhase; + ulong timestamp = 0; QPointF local; QPointF global; Qt::MouseButtons buttons; Qt::KeyboardModifiers modifiers; QPoint pixelDelta; QPoint angleDelta; + Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; + QWaylandWindow *surface = nullptr; }; } diff --git a/src/client/qwaylandnativeinterface.cpp b/src/client/qwaylandnativeinterface.cpp index 76acb526b..cf227d489 100644 --- a/src/client/qwaylandnativeinterface.cpp +++ b/src/client/qwaylandnativeinterface.cpp @@ -89,7 +89,7 @@ void *QWaylandNativeInterface::nativeResourceForWindow(const QByteArray &resourc return const_cast<wl_compositor *>(m_integration->display()->wl_compositor()); if (lowerCaseResource == "surface") { QWaylandWindow *w = static_cast<QWaylandWindow*>(window->handle()); - return w ? w->object() : nullptr; + return w ? w->wlSurface() : nullptr; } if (lowerCaseResource == "egldisplay" && m_integration->clientBufferIntegration()) diff --git a/src/client/qwaylandprimaryselectionv1.cpp b/src/client/qwaylandprimaryselectionv1.cpp new file mode 100644 index 000000000..3ddf6dac3 --- /dev/null +++ b/src/client/qwaylandprimaryselectionv1.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwaylandprimaryselectionv1_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandmimehelper_p.h" + +#include <QtGui/private/qguiapplication_p.h> + +#include <qpa/qplatformclipboard.h> + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandPrimarySelectionDeviceManagerV1::QWaylandPrimarySelectionDeviceManagerV1(QWaylandDisplay *display, uint id, uint version) + : zwp_primary_selection_device_manager_v1(display->wl_registry(), id, qMin(version, uint(1))) + , m_display(display) +{ + // Create devices for all seats. + // This only works if we get the global before all devices + const auto seats = m_display->inputDevices(); + for (auto *seat : seats) + seat->setPrimarySelectionDevice(createDevice(seat)); +} + +QWaylandPrimarySelectionDeviceV1 *QWaylandPrimarySelectionDeviceManagerV1::createDevice(QWaylandInputDevice *seat) +{ + return new QWaylandPrimarySelectionDeviceV1(this, seat); +} + +QWaylandPrimarySelectionOfferV1::QWaylandPrimarySelectionOfferV1(QWaylandDisplay *display, ::zwp_primary_selection_offer_v1 *offer) + : zwp_primary_selection_offer_v1(offer) + , m_display(display) + , m_mimeData(new QWaylandMimeData(this)) +{} + +void QWaylandPrimarySelectionOfferV1::startReceiving(const QString &mimeType, int fd) +{ + receive(mimeType, fd); + wl_display_flush(m_display->wl_display()); +} + +void QWaylandPrimarySelectionOfferV1::zwp_primary_selection_offer_v1_offer(const QString &mime_type) +{ + m_mimeData->appendFormat(mime_type); +} + +QWaylandPrimarySelectionDeviceV1::QWaylandPrimarySelectionDeviceV1( + QWaylandPrimarySelectionDeviceManagerV1 *manager, QWaylandInputDevice *seat) + : QtWayland::zwp_primary_selection_device_v1(manager->get_device(seat->wl_seat())) + , m_display(manager->display()) + , m_seat(seat) +{ +} + +QWaylandPrimarySelectionDeviceV1::~QWaylandPrimarySelectionDeviceV1() +{ + destroy(); +} + +void QWaylandPrimarySelectionDeviceV1::setSelectionSource(QWaylandPrimarySelectionSourceV1 *source) +{ + if (source) { + connect(source, &QWaylandPrimarySelectionSourceV1::cancelled, this, [this]() { + m_selectionSource.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); + }); + } + set_selection(source ? source->object() : nullptr, m_seat->serial()); + m_selectionSource.reset(source); +} + +void QWaylandPrimarySelectionDeviceV1::zwp_primary_selection_device_v1_data_offer(zwp_primary_selection_offer_v1 *offer) +{ + new QWaylandPrimarySelectionOfferV1(m_display, offer); +} + +void QWaylandPrimarySelectionDeviceV1::zwp_primary_selection_device_v1_selection(zwp_primary_selection_offer_v1 *id) +{ + + if (id) + m_selectionOffer.reset(static_cast<QWaylandPrimarySelectionOfferV1 *>(zwp_primary_selection_offer_v1_get_user_data(id))); + else + m_selectionOffer.reset(); + + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); +} + +QWaylandPrimarySelectionSourceV1::QWaylandPrimarySelectionSourceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QMimeData *mimeData) + : QtWayland::zwp_primary_selection_source_v1(manager->create_source()) + , m_mimeData(mimeData) +{ + if (!mimeData) + return; + for (auto &format : mimeData->formats()) + offer(format); +} + +QWaylandPrimarySelectionSourceV1::~QWaylandPrimarySelectionSourceV1() +{ + destroy(); +} + +void QWaylandPrimarySelectionSourceV1::zwp_primary_selection_source_v1_send(const QString &mime_type, int32_t fd) +{ + QByteArray content = QWaylandMimeHelper::getByteArray(m_mimeData, mime_type); + if (!content.isEmpty()) { + // Create a sigpipe handler that does nothing, or clients may be forced to terminate + // if the pipe is closed in the other end. + struct sigaction action, oldAction; + action.sa_handler = SIG_IGN; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + + sigaction(SIGPIPE, &action, &oldAction); + write(fd, content.constData(), size_t(content.size())); + sigaction(SIGPIPE, &oldAction, nullptr); + } + close(fd); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/client/qwaylandprimaryselectionv1_p.h b/src/client/qwaylandprimaryselectionv1_p.h new file mode 100644 index 000000000..b165c51b8 --- /dev/null +++ b/src/client/qwaylandprimaryselectionv1_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWAYLANDPRIMARYSELECTIONV1_P_H +#define QWAYLANDPRIMARYSELECTIONV1_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h> + +#include <QtWaylandClient/private/qtwaylandclientglobal_p.h> +#include <QtWaylandClient/private/qwaylanddataoffer_p.h> + +#include <QtCore/QObject> + +QT_REQUIRE_CONFIG(wayland_client_primary_selection); + +QT_BEGIN_NAMESPACE + +class QMimeData; + +namespace QtWaylandClient { + +class QWaylandInputDevice; +class QWaylandPrimarySelectionDeviceV1; + +class QWaylandPrimarySelectionDeviceManagerV1 : public QtWayland::zwp_primary_selection_device_manager_v1 +{ +public: + explicit QWaylandPrimarySelectionDeviceManagerV1(QWaylandDisplay *display, uint id, uint version); + QWaylandPrimarySelectionDeviceV1 *createDevice(QWaylandInputDevice *seat); + QWaylandDisplay *display() const { return m_display; } + +private: + QWaylandDisplay *m_display = nullptr; +}; + +class QWaylandPrimarySelectionOfferV1 : public QtWayland::zwp_primary_selection_offer_v1, public QWaylandAbstractDataOffer +{ +public: + explicit QWaylandPrimarySelectionOfferV1(QWaylandDisplay *display, ::zwp_primary_selection_offer_v1 *offer); + ~QWaylandPrimarySelectionOfferV1() override { destroy(); } + void startReceiving(const QString &mimeType, int fd) override; + QMimeData *mimeData() override { return m_mimeData.data(); } + +protected: + void zwp_primary_selection_offer_v1_offer(const QString &mime_type) override; + +private: + QWaylandDisplay *m_display = nullptr; + QScopedPointer<QWaylandMimeData> m_mimeData; +}; + +class Q_WAYLAND_CLIENT_EXPORT QWaylandPrimarySelectionSourceV1 : public QObject, public QtWayland::zwp_primary_selection_source_v1 +{ + Q_OBJECT +public: + explicit QWaylandPrimarySelectionSourceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QMimeData *mimeData); + ~QWaylandPrimarySelectionSourceV1() override; + + QMimeData *mimeData() const { return m_mimeData; } + +signals: + void cancelled(); + +protected: + void zwp_primary_selection_source_v1_send(const QString &mime_type, int32_t fd) override; + void zwp_primary_selection_source_v1_cancelled() override { emit cancelled(); } + +private: + QWaylandDisplay *m_display = nullptr; + QMimeData *m_mimeData = nullptr; +}; + +class QWaylandPrimarySelectionDeviceV1 : public QObject, public QtWayland::zwp_primary_selection_device_v1 +{ + Q_OBJECT + QWaylandPrimarySelectionDeviceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QWaylandInputDevice *seat); + +public: + ~QWaylandPrimarySelectionDeviceV1() override; + QWaylandPrimarySelectionOfferV1 *selectionOffer() const { return m_selectionOffer.data(); } + void invalidateSelectionOffer() { m_selectionOffer.reset(); } + QWaylandPrimarySelectionSourceV1 *selectionSource() const { return m_selectionSource.data(); } + void setSelectionSource(QWaylandPrimarySelectionSourceV1 *source); + +protected: + void zwp_primary_selection_device_v1_data_offer(struct ::zwp_primary_selection_offer_v1 *offer) override; + void zwp_primary_selection_device_v1_selection(struct ::zwp_primary_selection_offer_v1 *id) override; + +private: + QWaylandDisplay *m_display = nullptr; + QWaylandInputDevice *m_seat = nullptr; + QScopedPointer<QWaylandPrimarySelectionOfferV1> m_selectionOffer; + QScopedPointer<QWaylandPrimarySelectionSourceV1> m_selectionSource; + friend class QWaylandPrimarySelectionDeviceManagerV1; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDPRIMARYSELECTIONV1_P_H diff --git a/src/client/qwaylandqtkey.cpp b/src/client/qwaylandqtkey.cpp index a60185bd6..192619738 100644 --- a/src/client/qwaylandqtkey.cpp +++ b/src/client/qwaylandqtkey.cpp @@ -70,7 +70,11 @@ void QWaylandQtKeyExtension::zqt_key_v1_key(struct wl_surface *surface, } QWaylandInputDevice *dev = inputDevices.first(); - QWaylandWindow *win = surface ? QWaylandWindow::fromWlSurface(surface) : dev->keyboardFocus(); + + auto *win = surface ? QWaylandWindow::fromWlSurface(surface) : nullptr; + + if (!win) + win = dev->keyboardFocus(); if (!win || !win->window()) { qWarning("qt_key_extension: handle_qtkey: No keyboard focus"); diff --git a/src/client/qwaylandscreen.cpp b/src/client/qwaylandscreen.cpp index 5b04ae609..d116a807b 100644 --- a/src/client/qwaylandscreen.cpp +++ b/src/client/qwaylandscreen.cpp @@ -217,8 +217,9 @@ QWaylandScreen * QWaylandScreen::waylandScreenFromWindow(QWindow *window) QWaylandScreen *QWaylandScreen::fromWlOutput(::wl_output *output) { - auto wlOutput = static_cast<QtWayland::wl_output *>(wl_output_get_user_data(output)); - return static_cast<QWaylandScreen *>(wlOutput); + if (auto *o = QtWayland::wl_output::fromObject(output)) + return static_cast<QWaylandScreen *>(o); + return nullptr; } void QWaylandScreen::output_mode(uint32_t flags, int width, int height, int refresh) diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index 34044ec9b..c16d346eb 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -288,12 +288,15 @@ void QWaylandShmBackingStore::resize(const QSize &size) buffer = getBuffer(sizeWithMargins); } - qsizetype oldSize = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; + qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; + qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); + // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway - if (mBackBuffer != buffer && oldSize == buffer->image()->sizeInBytes()) { - memcpy(buffer->image()->bits(), mBackBuffer->image()->constBits(), buffer->image()->sizeInBytes()); - } + if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) + memcpy(buffer->image()->bits(), mBackBuffer->image()->constBits(), newSizeInBytes); + mBackBuffer = buffer; + // ensure the new buffer is at the beginning of the list so next time getBuffer() will pick // it if possible if (mBuffers.first() != buffer) { @@ -301,7 +304,7 @@ void QWaylandShmBackingStore::resize(const QSize &size) mBuffers.prepend(buffer); } - if (windowDecoration() && window()->isVisible()) + if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) windowDecoration()->update(); } diff --git a/src/client/qwaylandsurface.cpp b/src/client/qwaylandsurface.cpp new file mode 100644 index 000000000..c35f01b56 --- /dev/null +++ b/src/client/qwaylandsurface.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the config.tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwaylandsurface_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandscreen_p.h" + +#include <QtGui/QGuiApplication> + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandSurface::QWaylandSurface(QWaylandDisplay *display) + : wl_surface(display->createSurface(this)) +{ + connect(qApp, &QGuiApplication::screenRemoved, this, &QWaylandSurface::handleScreenRemoved); +} + +QWaylandSurface::~QWaylandSurface() +{ + destroy(); +} + +QWaylandScreen *QWaylandSurface::oldestEnteredScreen() +{ + return m_screens.value(0, nullptr); +} + +QWaylandSurface *QWaylandSurface::fromWlSurface(::wl_surface *surface) +{ + if (auto *s = QtWayland::wl_surface::fromObject(surface)) + return static_cast<QWaylandSurface *>(s); + return nullptr; +} + +void QWaylandSurface::handleScreenRemoved(QScreen *qScreen) +{ + auto *screen = static_cast<QWaylandScreen *>(qScreen->handle()); + if (m_screens.removeOne(screen)) + emit screensChanged(); +} + +void QWaylandSurface::surface_enter(wl_output *output) +{ + auto addedScreen = QWaylandScreen::fromWlOutput(output); + + if (!addedScreen) + return; + + if (m_screens.contains(addedScreen)) { + qCWarning(lcQpaWayland) + << "Ignoring unexpected wl_surface.enter received for output with id:" + << wl_proxy_get_id(reinterpret_cast<wl_proxy *>(output)) + << "screen name:" << addedScreen->name() << "screen model:" << addedScreen->model() + << "This is most likely a bug in the compositor."; + return; + } + + m_screens.append(addedScreen); + emit screensChanged(); +} + +void QWaylandSurface::surface_leave(wl_output *output) +{ + auto *removedScreen = QWaylandScreen::fromWlOutput(output); + + if (!removedScreen) + return; + + bool wasRemoved = m_screens.removeOne(removedScreen); + if (!wasRemoved) { + qCWarning(lcQpaWayland) + << "Ignoring unexpected wl_surface.leave received for output with id:" + << wl_proxy_get_id(reinterpret_cast<wl_proxy *>(output)) + << "screen name:" << removedScreen->name() + << "screen model:" << removedScreen->model() + << "This is most likely a bug in the compositor."; + return; + } + emit screensChanged(); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/client/qwaylandsurface_p.h b/src/client/qwaylandsurface_p.h new file mode 100644 index 000000000..541010934 --- /dev/null +++ b/src/client/qwaylandsurface_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the config.tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWAYLANDSURFACE_P_H +#define QWAYLANDSURFACE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/QScreen> + +#include <QtWaylandClient/private/qwayland-wayland.h> + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandScreen; +class QWaylandWindow; +class QWaylandDisplay; + +class QWaylandSurface : public QObject, public QtWayland::wl_surface +{ + Q_OBJECT +public: + explicit QWaylandSurface(QWaylandDisplay *display); + ~QWaylandSurface() override; + QWaylandScreen *oldestEnteredScreen(); + QWaylandWindow *waylandWindow() const { return m_window; } + + static QWaylandSurface *fromWlSurface(::wl_surface *surface); + +signals: + void screensChanged(); + +private slots: + void handleScreenRemoved(QScreen *qScreen); + +protected: + void surface_enter(struct ::wl_output *output) override; + void surface_leave(struct ::wl_output *output) override; + + QVector<QWaylandScreen *> m_screens; //As seen by wl_surface.enter/leave events. Chronological order. + QWaylandWindow *m_window = nullptr; + + friend class QWaylandWindow; // TODO: shouldn't need to be friends +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDSURFACE_P_H diff --git a/src/client/qwaylandtouch.cpp b/src/client/qwaylandtouch.cpp index 48c869a60..0394aef31 100644 --- a/src/client/qwaylandtouch.cpp +++ b/src/client/qwaylandtouch.cpp @@ -40,6 +40,7 @@ #include "qwaylandtouch_p.h" #include "qwaylandinputdevice_p.h" #include "qwaylanddisplay_p.h" +#include "qwaylandsurface_p.h" QT_BEGIN_NAMESPACE diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index 4532bc236..9bc400f8a 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -41,6 +41,7 @@ #include "qwaylandbuffer_p.h" #include "qwaylanddisplay_p.h" +#include "qwaylandsurface_p.h" #include "qwaylandinputdevice_p.h" #include "qwaylandscreen_p.h" #include "qwaylandshellsurface_p.h" @@ -80,7 +81,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window) { static WId id = 1; mWindowId = id++; - connect(qApp, &QGuiApplication::screenRemoved, this, &QWaylandWindow::handleScreenRemoved); initializeWlSurface(); } @@ -90,7 +90,7 @@ QWaylandWindow::~QWaylandWindow() delete mWindowDecoration; - if (isInitialized()) + if (mSurface) reset(false); const QWindow *parent = window(); @@ -115,7 +115,7 @@ void QWaylandWindow::initWindow() if (window()->type() == Qt::Desktop) return; - if (!isInitialized()) { + if (!mSurface) { initializeWlSurface(); QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceCreated); QGuiApplication::sendEvent(window(), &e); @@ -181,7 +181,7 @@ void QWaylandWindow::initWindow() // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() // to inform the compositor that high-resolution buffers will be provided. if (mDisplay->compositorVersion() >= 3) - set_buffer_scale(scale()); + mSurface->set_buffer_scale(scale()); if (QScreen *s = window()->screen()) setOrientationMask(s->orientationUpdateMask()); @@ -199,7 +199,11 @@ void QWaylandWindow::initWindow() void QWaylandWindow::initializeWlSurface() { - init(mDisplay->createSurface(static_cast<QtWayland::wl_surface *>(this))); + Q_ASSERT(!mSurface); + mSurface.reset(new QWaylandSurface(mDisplay)); + connect(mSurface.data(), &QWaylandSurface::screensChanged, + this, &QWaylandWindow::handleScreensChanged); + mSurface->m_window = this; } bool QWaylandWindow::shouldCreateShellSurface() const @@ -226,7 +230,7 @@ bool QWaylandWindow::shouldCreateSubSurface() const void QWaylandWindow::reset(bool sendDestroyEvent) { - if (isInitialized() && sendDestroyEvent) { + if (mSurface && sendDestroyEvent) { QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed); QGuiApplication::sendEvent(window(), &e); } @@ -234,11 +238,10 @@ void QWaylandWindow::reset(bool sendDestroyEvent) mShellSurface = nullptr; delete mSubSurfaceWindow; mSubSurfaceWindow = nullptr; - if (isInitialized()) { + if (mSurface) { emit wlSurfaceDestroyed(); - destroy(); + mSurface.reset(); } - mScreens.clear(); if (mFrameCallback) { wl_callback_destroy(mFrameCallback); @@ -251,7 +254,9 @@ void QWaylandWindow::reset(bool sendDestroyEvent) QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface) { - return static_cast<QWaylandWindow *>(static_cast<QtWayland::wl_surface *>(wl_surface_get_user_data(surface))); + if (auto *s = QWaylandSurface::fromWlSurface(surface)) + return s->m_window; + return nullptr; } WId QWaylandWindow::winId() const @@ -381,7 +386,12 @@ void QWaylandWindow::closePopups(QWaylandWindow *parent) QWaylandScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const { - return mScreens.isEmpty() ? waylandScreen() : mScreens.first(); + if (mSurface) { + if (auto *screen = mSurface->oldestEnteredScreen()) + return screen; + } + + return waylandScreen(); } void QWaylandWindow::setVisible(bool visible) @@ -425,18 +435,18 @@ void QWaylandWindow::setMask(const QRegion &mask) mMask = mask; - if (!isInitialized()) + if (!mSurface) return; if (mMask.isEmpty()) { - set_input_region(nullptr); + mSurface->set_input_region(nullptr); } else { struct ::wl_region *region = mDisplay->createRegion(mMask); - set_input_region(region); + mSurface->set_input_region(region); wl_region_destroy(region); } - wl_surface::commit(); + mSurface->commit(); } void QWaylandWindow::applyConfigureWhenPossible() @@ -490,58 +500,6 @@ void QWaylandWindow::applyConfigure() QWindowSystemInterface::flushWindowSystemEvents(); } -void QWaylandWindow::surface_enter(wl_output *output) -{ - QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); - auto addedScreen = QWaylandScreen::fromWlOutput(output); - - if (mScreens.contains(addedScreen)) { - qCWarning(lcQpaWayland) - << "Ignoring unexpected wl_surface.enter received for output with id:" - << wl_proxy_get_id(reinterpret_cast<wl_proxy *>(output)) - << "screen name:" << addedScreen->name() << "screen model:" << addedScreen->model() - << "This is most likely a bug in the compositor."; - return; - } - - mScreens.append(addedScreen); - - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (oldScreen != newScreen) //currently this will only happen if the first wl_surface.enter is for a non-primary screen - handleScreenChanged(); -} - -void QWaylandWindow::surface_leave(wl_output *output) -{ - QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); - auto *removedScreen = QWaylandScreen::fromWlOutput(output); - bool wasRemoved = mScreens.removeOne(removedScreen); - if (!wasRemoved) { - qCWarning(lcQpaWayland) - << "Ignoring unexpected wl_surface.leave received for output with id:" - << wl_proxy_get_id(reinterpret_cast<wl_proxy *>(output)) - << "screen name:" << removedScreen->name() - << "screen model:" << removedScreen->model() - << "This is most likely a bug in the compositor."; - return; - } - - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (oldScreen != newScreen) - handleScreenChanged(); -} - -void QWaylandWindow::handleScreenRemoved(QScreen *qScreen) -{ - QWaylandScreen *oldScreen = calculateScreenFromSurfaceEvents(); - bool wasRemoved = mScreens.removeOne(static_cast<QWaylandScreen *>(qScreen->handle())); - if (wasRemoved) { - QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); - if (oldScreen != newScreen) - handleScreenChanged(); - } -} - void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { Q_ASSERT(!buffer->committed()); @@ -549,9 +507,9 @@ void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) handleUpdate(); buffer->setBusy(); - QtWayland::wl_surface::attach(buffer->buffer(), x, y); + mSurface->attach(buffer->buffer(), x, y); } else { - QtWayland::wl_surface::attach(nullptr, 0, 0); + mSurface->attach(nullptr, 0, 0); } } @@ -563,7 +521,7 @@ void QWaylandWindow::attachOffset(QWaylandBuffer *buffer) void QWaylandWindow::damage(const QRect &rect) { - damage(rect.x(), rect.y(), rect.width(), rect.height()); + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); } void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) @@ -593,20 +551,20 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) qCDebug(lcWaylandBackingstore) << "Buffer already committed, ignoring."; return; } - if (!isInitialized()) + if (!mSurface) return; attachOffset(buffer); for (const QRect &rect: damage) - wl_surface::damage(rect.x(), rect.y(), rect.width(), rect.height()); + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); Q_ASSERT(!buffer->committed()); buffer->setCommitted(); - wl_surface::commit(); + mSurface->commit(); } void QWaylandWindow::commit() { - wl_surface::commit(); + mSurface->commit(); } const wl_callback_listener QWaylandWindow::callbackListener = { @@ -691,6 +649,11 @@ QRect QWaylandWindow::windowContentGeometry() const return QRect(QPoint(), surfaceSize()); } +wl_surface *QWaylandWindow::wlSurface() +{ + return mSurface ? mSurface->object() : nullptr; +} + QWaylandShellSurface *QWaylandWindow::shellSurface() const { return mShellSurface; @@ -732,9 +695,9 @@ void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orient default: Q_UNREACHABLE(); } - set_buffer_transform(transform); + mSurface->set_buffer_transform(transform); // set_buffer_transform is double buffered, we need to commit. - wl_surface::commit(); + mSurface->commit(); } void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask) @@ -866,6 +829,18 @@ QWaylandWindow *QWaylandWindow::transientParent() const void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e) { + if (e.type == QWaylandPointerEvent::Leave) { + if (mWindowDecoration) { + if (mMouseEventsInContentArea) + QWindowSystemInterface::handleLeaveEvent(window()); + } else { + QWindowSystemInterface::handleLeaveEvent(window()); + } +#if QT_CONFIG(cursor) + restoreMouseCursor(inputDevice); +#endif + return; + } if (mWindowDecoration) { handleMouseEventWithDecoration(inputDevice, e); @@ -874,11 +849,15 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan case QWaylandPointerEvent::Enter: QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global); break; + case QWaylandPointerEvent::Press: + case QWaylandPointerEvent::Release: case QWaylandPointerEvent::Motion: QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, e.local, e.global, e.buttons, e.modifiers); break; case QWaylandPointerEvent::Wheel: - QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global, e.pixelDelta, e.angleDelta, e.modifiers); + QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global, + e.pixelDelta, e.angleDelta, e.modifiers, + e.phase, e.source, false); break; } } @@ -892,20 +871,6 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan #endif } -void QWaylandWindow::handleMouseLeave(QWaylandInputDevice *inputDevice) -{ - if (mWindowDecoration) { - if (mMouseEventsInContentArea) { - QWindowSystemInterface::handleLeaveEvent(window()); - } - } else { - QWindowSystemInterface::handleLeaveEvent(window()); - } -#if QT_CONFIG(cursor) - restoreMouseCursor(inputDevice); -#endif -} - bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods) { if (!mWindowDecoration) @@ -947,12 +912,18 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe case QWaylandPointerEvent::Enter: QWindowSystemInterface::handleEnterEvent(window(), localTranslated, globalTranslated); break; + case QWaylandPointerEvent::Press: + case QWaylandPointerEvent::Release: case QWaylandPointerEvent::Motion: QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, localTranslated, globalTranslated, e.buttons, e.modifiers); break; - case QWaylandPointerEvent::Wheel: - QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, localTranslated, globalTranslated, e.pixelDelta, e.angleDelta, e.modifiers); + case QWaylandPointerEvent::Wheel: { + QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, + localTranslated, globalTranslated, + e.pixelDelta, e.angleDelta, e.modifiers, + e.phase, e.source, false); break; + } } mMouseEventsInContentArea = true; @@ -965,16 +936,21 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe } } -void QWaylandWindow::handleScreenChanged() +void QWaylandWindow::handleScreensChanged() { QWaylandScreen *newScreen = calculateScreenFromSurfaceEvents(); + + if (newScreen == mLastReportedScreen) + return; + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); + mLastReportedScreen = newScreen; int scale = newScreen->scale(); if (scale != mScale) { mScale = scale; - if (isInitialized() && mDisplay->compositorVersion() >= 3) - set_buffer_scale(mScale); + if (mSurface && mDisplay->compositorVersion() >= 3) + mSurface->set_buffer_scale(mScale); ensureSize(); } } @@ -1158,7 +1134,7 @@ void QWaylandWindow::handleUpdate() QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); } - mFrameCallback = frame(); + mFrameCallback = mSurface->frame(); wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); mWaitingForFrameCallback = true; mWaitingForUpdate = false; diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 8c1ebe167..5eadc02c8 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -79,8 +79,9 @@ class QWaylandInputDevice; class QWaylandScreen; class QWaylandShmBackingStore; class QWaylandPointerEvent; +class QWaylandSurface; -class Q_WAYLAND_CLIENT_EXPORT QWaylandWindow : public QObject, public QPlatformWindow, public QtWayland::wl_surface +class Q_WAYLAND_CLIENT_EXPORT QWaylandWindow : public QObject, public QPlatformWindow { Q_OBJECT public: @@ -108,12 +109,10 @@ public: void applyConfigureWhenPossible(); //rename to possible? - using QtWayland::wl_surface::attach; void attach(QWaylandBuffer *buffer, int x, int y); void attachOffset(QWaylandBuffer *buffer); QPoint attachOffset() const; - using QtWayland::wl_surface::damage; void damage(const QRect &rect); void safeCommit(QWaylandBuffer *buffer, const QRegion &damage); @@ -128,6 +127,8 @@ public: QSize surfaceSize() const; QRect windowContentGeometry() const; + QWaylandSurface *waylandSurface() const { return mSurface.data(); } + ::wl_surface *wlSurface(); static QWaylandWindow *fromWlSurface(::wl_surface *surface); QWaylandDisplay *display() const { return mDisplay; } @@ -157,7 +158,6 @@ public: QWaylandAbstractDecoration *decoration() const; void handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); - void handleMouseLeave(QWaylandInputDevice *inputDevice); bool touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods); @@ -206,11 +206,8 @@ signals: void wlSurfaceDestroyed(); protected: - void surface_enter(struct ::wl_output *output) override; - void surface_leave(struct ::wl_output *output) override; - - QVector<QWaylandScreen *> mScreens; //As seen by wl_surface.enter/leave events. Chronological order. QWaylandDisplay *mDisplay = nullptr; + QScopedPointer<QWaylandSurface> mSurface; QWaylandShellSurface *mShellSurface = nullptr; QWaylandSubSurface *mSubSurfaceWindow = nullptr; QVector<QWaylandSubSurface *> mChildren; @@ -241,6 +238,7 @@ protected: bool mSentInitialResize = false; QPoint mOffset; int mScale = 1; + QWaylandScreen *mLastReportedScreen = nullptr; QIcon mWindowIcon; @@ -252,9 +250,6 @@ protected: QWaylandBuffer *mQueuedBuffer = nullptr; QRegion mQueuedBufferDamage; -private slots: - void handleScreenRemoved(QScreen *qScreen); - private: void setGeometry_helper(const QRect &rect); void initWindow(); @@ -267,7 +262,7 @@ private: QWaylandScreen *calculateScreenFromSurfaceEvents() const; void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); - void handleScreenChanged(); + void handleScreensChanged(); QRect mLastExposeGeometry; diff --git a/src/compositor/compositor_api/compositor_api.pri b/src/compositor/compositor_api/compositor_api.pri index 8dbe12ac1..49d0de414 100644 --- a/src/compositor/compositor_api/compositor_api.pri +++ b/src/compositor/compositor_api/compositor_api.pri @@ -64,23 +64,28 @@ qtConfig(draganddrop) { compositor_api/qwaylanddrag.cpp } -qtHaveModule(quick):qtConfig(opengl) { +qtHaveModule(quick) { DEFINES += QT_WAYLAND_COMPOSITOR_QUICK SOURCES += \ compositor_api/qwaylandquickcompositor.cpp \ compositor_api/qwaylandquicksurface.cpp \ compositor_api/qwaylandquickoutput.cpp \ - compositor_api/qwaylandquickitem.cpp \ - compositor_api/qwaylandquickhardwarelayer.cpp + compositor_api/qwaylandquickitem.cpp HEADERS += \ compositor_api/qwaylandquickcompositor.h \ compositor_api/qwaylandquicksurface.h \ compositor_api/qwaylandquickoutput.h \ compositor_api/qwaylandquickitem.h \ - compositor_api/qwaylandquickitem_p.h \ - compositor_api/qwaylandquickhardwarelayer_p.h + compositor_api/qwaylandquickitem_p.h + + qtHaveModule(opengl) { + SOURCES += \ + compositor_api/qwaylandquickhardwarelayer.cpp + HEADERS += \ + compositor_api/qwaylandquickhardwarelayer_p.h + } QT += qml qml-private quick quick-private } diff --git a/src/compositor/compositor_api/qwaylandcompositor.cpp b/src/compositor/compositor_api/qwaylandcompositor.cpp index 8ff9e0905..530cf8ed6 100644 --- a/src/compositor/compositor_api/qwaylandcompositor.cpp +++ b/src/compositor/compositor_api/qwaylandcompositor.cpp @@ -254,8 +254,10 @@ QWaylandCompositorPrivate::~QWaylandCompositorPrivate() delete data_device_manager; #endif +#if QT_CONFIG(opengl) // Some client buffer integrations need to clean up before the destroying the wl_display client_buffer_integration.reset(); +#endif if (ownsDisplay) wl_display_destroy(display); diff --git a/src/compositor/compositor_api/qwaylandquickcompositor.cpp b/src/compositor/compositor_api/qwaylandquickcompositor.cpp index 14b592efb..1b3f93e5c 100644 --- a/src/compositor/compositor_api/qwaylandquickcompositor.cpp +++ b/src/compositor/compositor_api/qwaylandquickcompositor.cpp @@ -128,6 +128,7 @@ void QWaylandQuickCompositor::grabSurface(QWaylandSurfaceGrabber *grabber, const return; } +#if QT_CONFIG(opengl) QWaylandQuickOutput *output = static_cast<QWaylandQuickOutput *>(defaultOutput()); if (!output) { emit grabber->failed(QWaylandSurfaceGrabber::RendererNotReady); @@ -169,6 +170,9 @@ void QWaylandQuickCompositor::grabSurface(QWaylandSurfaceGrabber *grabber, const state->grabber = grabber; state->buffer = buffer; static_cast<QQuickWindow *>(output->window())->scheduleRenderJob(state, QQuickWindow::NoStage); +#else + emit grabber->failed(QWaylandSurfaceGrabber::UnknownBufferType); +#endif } QT_END_NAMESPACE diff --git a/src/compositor/compositor_api/qwaylandquickitem.cpp b/src/compositor/compositor_api/qwaylandquickitem.cpp index 7b0d5c5d5..996177be4 100644 --- a/src/compositor/compositor_api/qwaylandquickitem.cpp +++ b/src/compositor/compositor_api/qwaylandquickitem.cpp @@ -73,6 +73,7 @@ QT_BEGIN_NAMESPACE +#if QT_CONFIG(opengl) static const struct { const char * const vertexShaderSourceFile; const char * const fragmentShaderSourceFile; @@ -259,6 +260,7 @@ void QWaylandBufferMaterial::ensureTextures(int count) m_textures << nullptr; } } +#endif // QT_CONFIG(opengl) QMutex *QWaylandQuickItemPrivate::mutex = nullptr; @@ -284,10 +286,12 @@ public: if (m_ref.hasBuffer()) { if (buffer.isSharedMemory()) { m_sgTex = surfaceItem->window()->createTextureFromImage(buffer.image()); - if (m_sgTex) { +#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()) { @@ -297,6 +301,9 @@ public: 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(); @@ -1327,7 +1334,11 @@ QSGNode *QWaylandQuickItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat const QRectF rect = invertY ? QRectF(0, height(), width(), -height()) : QRectF(0, 0, width(), height()); - if (ref.isSharedMemory() || bufferTypes[ref.bufferFormatEgl()].canProvideTexture) { + 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); @@ -1353,45 +1364,48 @@ QSGNode *QWaylandQuickItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat node->setSourceRect(QRectF(source.topLeft() * scale, source.size() * scale)); return node; - } else { - Q_ASSERT(!d->provider); + } - QSGGeometryNode *node = static_cast<QSGGeometryNode *>(oldNode); +#if QT_CONFIG(opengl) + Q_ASSERT(!d->provider); - if (!node) { - node = new QSGGeometryNode; - d->newTexture = true; - } + QSGGeometryNode *node = static_cast<QSGGeometryNode *>(oldNode); - QSGGeometry *geometry = node->geometry(); - QWaylandBufferMaterial *material = static_cast<QWaylandBufferMaterial *>(node->material()); + if (!node) { + node = new QSGGeometryNode; + d->newTexture = true; + } - if (!geometry) - geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); + QSGGeometry *geometry = node->geometry(); + QWaylandBufferMaterial *material = static_cast<QWaylandBufferMaterial *>(node->material()); - if (!material) - material = new QWaylandBufferMaterial(ref.bufferFormatEgl()); + if (!geometry) + geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); - 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(); - } + if (!material) + material = new QWaylandBufferMaterial(ref.bufferFormatEgl()); - QSGGeometry::updateTexturedRectGeometry(geometry, rect, QRectF(0, 0, 1, 1)); + 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(); + } - node->setGeometry(geometry); - node->setFlag(QSGNode::OwnsGeometry, true); + QSGGeometry::updateTexturedRectGeometry(geometry, rect, QRectF(0, 0, 1, 1)); - node->setMaterial(material); - node->setFlag(QSGNode::OwnsMaterial, true); + node->setGeometry(geometry); + node->setFlag(QSGNode::OwnsGeometry, true); - return node; - } + node->setMaterial(material); + node->setFlag(QSGNode::OwnsMaterial, true); - Q_UNREACHABLE(); + 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) diff --git a/src/compositor/compositor_api/qwaylandquickitem_p.h b/src/compositor/compositor_api/qwaylandquickitem_p.h index 3d710d71b..2ec02ca6d 100644 --- a/src/compositor/compositor_api/qwaylandquickitem_p.h +++ b/src/compositor/compositor_api/qwaylandquickitem_p.h @@ -64,6 +64,7 @@ class QWaylandSurfaceTextureProvider; class QMutex; class QOpenGLTexture; +#if QT_CONFIG(opengl) class QWaylandBufferMaterialShader : public QSGMaterialShader { public: @@ -102,6 +103,7 @@ private: const QWaylandBufferRef::BufferFormatEgl m_format; QVarLengthArray<QOpenGLTexture*, 3> m_textures; }; +#endif // QT_CONFIG(opengl) class QWaylandQuickItemPrivate : public QQuickItemPrivate { diff --git a/src/compositor/compositor_api/qwaylandseat.cpp b/src/compositor/compositor_api/qwaylandseat.cpp index bc5e1d8b2..0a6248d11 100644 --- a/src/compositor/compositor_api/qwaylandseat.cpp +++ b/src/compositor/compositor_api/qwaylandseat.cpp @@ -289,6 +289,10 @@ void QWaylandSeat::sendKeyReleaseEvent(uint code) * Sends a touch point event to the \a surface on a touch device with the given * \a id, \a point and \a state. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l QWaylandQuickItem::touchEventsEnabled or \l sendFullTouchEvent, + * as it might lead to conflicting touch ids. + * * Returns the serial for the touch up or touch down event. */ uint QWaylandSeat::sendTouchPointEvent(QWaylandSurface *surface, int id, const QPointF &point, Qt::TouchPointState state) @@ -310,6 +314,10 @@ uint QWaylandSeat::sendTouchPointEvent(QWaylandSurface *surface, int id, const Q * \note You need to send a touch frame event when you are done sending touch * events. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l WaylandQuickItem::touchEventsEnabled, as it might lead to + * conflicting touch ids. + * * Returns the serial for the touch down event. */ @@ -320,6 +328,10 @@ uint QWaylandSeat::sendTouchPointEvent(QWaylandSurface *surface, int id, const Q * \note You need to send a touch frame event when you are done sending touch * events. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l QWaylandQuickItem::touchEventsEnabled or \l sendFullTouchEvent, + * as it might lead to conflicting touch ids. + * * Returns the serial for the touch down event. */ uint QWaylandSeat::sendTouchPointPressed(QWaylandSurface *surface, int id, const QPointF &position) @@ -336,6 +348,10 @@ uint QWaylandSeat::sendTouchPointPressed(QWaylandSurface *surface, int id, const * \note You need to send a touch frame event when you are done sending touch * events. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l WaylandQuickItem::touchEventsEnabled, as it might lead to + * conflicting touch ids. + * * Returns the serial for the touch up event. */ @@ -346,6 +362,10 @@ uint QWaylandSeat::sendTouchPointPressed(QWaylandSurface *surface, int id, const * \note You need to send a touch frame event when you are done sending touch * events. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l QWaylandQuickItem::touchEventsEnabled or \l sendFullTouchEvent, + * as it might lead to conflicting touch ids. + * * Returns the serial for the touch up event. */ uint QWaylandSeat::sendTouchPointReleased(QWaylandSurface *surface, int id, const QPointF &position) @@ -362,6 +382,10 @@ uint QWaylandSeat::sendTouchPointReleased(QWaylandSurface *surface, int id, cons * \note You need to send a touch frame event when you are done sending touch * events. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l WaylandQuickItem::touchEventsEnabled, as it might lead to + * conflicting touch ids. + * * Returns the serial for the touch motion event. */ @@ -372,6 +396,10 @@ uint QWaylandSeat::sendTouchPointReleased(QWaylandSurface *surface, int id, cons * \note You need to send a touch frame event when you are done sending touch * events. * + * \warning This API should not be used in combination with forwarding of touch + * events using \l QWaylandQuickItem::touchEventsEnabled or \l sendFullTouchEvent, + * as it might lead to conflicting touch ids. + * * Returns the serial for the touch motion event. */ uint QWaylandSeat::sendTouchPointMoved(QWaylandSurface *surface, int id, const QPointF &position) @@ -415,6 +443,11 @@ void QWaylandSeat::sendTouchCancelEvent(QWaylandClient *client) /*! * Sends the \a event to the specified \a surface on the touch device. + * + * \warning This API will automatically map \l QTouchEvent::TouchPoint::id to a + * sequential id before sending it to the client. It should therefore not be + * used in combination with the other API using explicit ids, as collisions + * might occur. */ void QWaylandSeat::sendFullTouchEvent(QWaylandSurface *surface, QTouchEvent *event) { diff --git a/src/compositor/compositor_api/qwaylandtouch.cpp b/src/compositor/compositor_api/qwaylandtouch.cpp index 3e7298001..15746cb5f 100644 --- a/src/compositor/compositor_api/qwaylandtouch.cpp +++ b/src/compositor/compositor_api/qwaylandtouch.cpp @@ -98,6 +98,20 @@ void QWaylandTouchPrivate::sendMotion(QWaylandClient *client, uint32_t time, int wl_fixed_from_double(position.x()), wl_fixed_from_double(position.y())); } +int QWaylandTouchPrivate::toSequentialWaylandId(int touchId) +{ + const int waylandId = ids.indexOf(touchId); + if (waylandId != -1) + return waylandId; + const int availableId = ids.indexOf(-1); + if (availableId != -1) { + ids[availableId] = touchId; + return availableId; + } + ids.append(touchId); + return ids.length() - 1; +} + /*! * \class QWaylandTouch * \inmodule QtWaylandCompositor @@ -212,7 +226,10 @@ void QWaylandTouch::sendFullTouchEvent(QWaylandSurface *surface, QTouchEvent *ev for (int i = 0; i < pointCount; ++i) { const QTouchEvent::TouchPoint &tp(points.at(i)); // Convert the local pos in the compositor window to surface-relative. - sendTouchPointEvent(surface, tp.id(), tp.pos(), tp.state()); + const int id = d->toSequentialWaylandId(tp.id()); + sendTouchPointEvent(surface, id, tp.pos(), tp.state()); + if (tp.state() == Qt::TouchPointReleased) + d->ids[id] = -1; } sendFrameEvent(surface->client()); } diff --git a/src/compositor/compositor_api/qwaylandtouch_p.h b/src/compositor/compositor_api/qwaylandtouch_p.h index de1b748de..0b87f8475 100644 --- a/src/compositor/compositor_api/qwaylandtouch_p.h +++ b/src/compositor/compositor_api/qwaylandtouch_p.h @@ -80,8 +80,10 @@ public: private: void touch_release(Resource *resource) override; + int toSequentialWaylandId(int touchId); QWaylandSeat *seat = nullptr; + QVarLengthArray<int, 10> ids; }; QT_END_NAMESPACE diff --git a/src/compositor/configure.json b/src/compositor/configure.json index aabd2472f..db80543f7 100644 --- a/src/compositor/configure.json +++ b/src/compositor/configure.json @@ -86,6 +86,11 @@ "type": "compile", "test": "dmabuf_client_buffer", "use": "egl" + }, + "vulkan-server-buffer": { + "label": "Vulkan Buffer Sharing", + "type": "compile", + "test": "vulkan_server_buffer" } }, @@ -139,6 +144,11 @@ "condition": "features.wayland-server && features.opengl && features.egl && tests.dmabuf-client-buffer", "output": [ "privateFeature" ] }, + "wayland-vulkan-server-buffer": { + "label": "Vulkan-based server buffer integration", + "condition": "features.wayland-server && features.opengl && features.egl && tests.vulkan-server-buffer", + "output": [ "privateFeature" ] + }, "wayland-shm-emulation-server-buffer": { "label": "Shm emulation server buffer", "condition": "features.wayland-server && features.opengl", diff --git a/src/compositor/extensions/extensions.pri b/src/compositor/extensions/extensions.pri index 361f93984..206bde20d 100644 --- a/src/compositor/extensions/extensions.pri +++ b/src/compositor/extensions/extensions.pri @@ -65,24 +65,36 @@ SOURCES += \ extensions/qwaylandiviapplication.cpp \ extensions/qwaylandivisurface.cpp \ -qtHaveModule(quick):contains(QT_CONFIG, opengl) { +qtHaveModule(quick) { HEADERS += \ + extensions/qwaylandquickshellintegration.h \ extensions/qwaylandquickshellsurfaceitem.h \ extensions/qwaylandquickshellsurfaceitem_p.h \ extensions/qwaylandivisurfaceintegration_p.h \ extensions/qwaylandwlshellintegration_p.h \ extensions/qwaylandxdgshellv5integration_p.h \ extensions/qwaylandxdgshellv6integration_p.h \ - extensions/qwaylandxdgshellintegration_p.h + extensions/qwaylandxdgshellintegration_p.h \ SOURCES += \ + extensions/qwaylandquickshellintegration.cpp \ extensions/qwaylandquickshellsurfaceitem.cpp \ extensions/qwaylandivisurfaceintegration.cpp \ extensions/qwaylandwlshellintegration.cpp \ extensions/qwaylandxdgshellv5integration.cpp \ extensions/qwaylandxdgshellv6integration.cpp \ - extensions/qwaylandxdgshellintegration.cpp + extensions/qwaylandxdgshellintegration.cpp \ + qtHaveModule(opengl) { + WAYLANDSERVERSOURCES += \ + ../extensions/qt-texture-sharing-unstable-v1.xml + + HEADERS += \ + extensions/qwltexturesharingextension_p.h + + SOURCES += \ + extensions/qwltexturesharingextension.cpp + } } include ($$PWD/pregenerated/xdg-shell-v5.pri) diff --git a/src/compositor/extensions/qwaylandquickshellintegration.cpp b/src/compositor/extensions/qwaylandquickshellintegration.cpp new file mode 100644 index 000000000..961b7b111 --- /dev/null +++ b/src/compositor/extensions/qwaylandquickshellintegration.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwaylandquickshellintegration.h" + +/*! + * \class QWaylandQuickShellIntegration + * \inmodule QtWaylandCompositor + * \since 5.14 + * \brief Provides support for shell surface integration with QtQuick + * + * Shell surface implementations should inherit from this class in order to provide + * an integration between the shell surface and QtQuick. + * + * \sa QWaylandShellSurface + * \sa QWaylandShellSurfaceItem + */ + +QWaylandQuickShellIntegration::QWaylandQuickShellIntegration(QObject *parent) + : QObject(parent) +{ +} + +/*! + * This method can be reimplemented in a subclass to receive touch events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::touchEvent() + */ +bool QWaylandQuickShellIntegration::touchEvent(QTouchEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive hover-enter events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Hover events are only provided if \l {QWaylandQuickShellSurfaceItem::} {acceptHoverEvents()} + * is \a true. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::hoverEnterEvent() + */ +bool QWaylandQuickShellIntegration::hoverEnterEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive hover-leave events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Hover events are only provided if \l {QWaylandQuickShellSurfaceItem::} {acceptHoverEvents()} + * is \a true. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::hoverLeaveEvent() + */ +bool QWaylandQuickShellIntegration::hoverLeaveEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive hover-move events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Hover events are only provided if \l {QWaylandQuickShellSurfaceItem::} {acceptHoverEvents()} + * is \a true. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::hoverMoveEvent() + */ +bool QWaylandQuickShellIntegration::hoverMoveEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive key press events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::keyPressEvent() + */ +bool QWaylandQuickShellIntegration::keyPressEvent(QKeyEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive key release events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::keyReleaseEvent() + */ +bool QWaylandQuickShellIntegration::keyReleaseEvent(QKeyEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive mouse double click events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::mouseDoubleClickEvent() + */ +bool QWaylandQuickShellIntegration::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive mouse move events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::mouseMoveEvent() + */ +bool QWaylandQuickShellIntegration::mouseMoveEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive mouse press events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::mousePressEvent() + */ +bool QWaylandQuickShellIntegration::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + * This method can be reimplemented in a subclass to receive mouse release events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::mouseReleaseEvent() + */ +bool QWaylandQuickShellIntegration::mouseReleaseEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + return false; +} + +#if QT_CONFIG(wheelevent) +/*! + * This method can be reimplemented in a subclass to receive wheel events + * for a shell surface. + * + * The event information is provided by the \a event parameter. + * + * Return \a false if you want QWaylandQuickShellSurfaceItem to handle + * the event. + * + * \sa QWaylandQuickShellSurfaceItem::wheelEvent() + */ +bool QWaylandQuickShellIntegration::wheelEvent(QWheelEvent *event) +{ + Q_UNUSED(event); + return false; +} +#endif diff --git a/src/compositor/extensions/qwaylandquickshellintegration.h b/src/compositor/extensions/qwaylandquickshellintegration.h new file mode 100644 index 000000000..e1a4c2384 --- /dev/null +++ b/src/compositor/extensions/qwaylandquickshellintegration.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWAYLANDQUICKSHELLINTEGRATION_H +#define QWAYLANDQUICKSHELLINTEGRATION_H + +#include <QtCore/QObject> +#include <QtGui/QMouseEvent> +#include <QtWaylandCompositor/qtwaylandcompositorglobal.h> + +QT_BEGIN_NAMESPACE + +class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandQuickShellIntegration : public QObject +{ + Q_OBJECT +public: + explicit QWaylandQuickShellIntegration(QObject *parent = nullptr); + + virtual bool touchEvent(QTouchEvent *event); + + virtual bool hoverEnterEvent(QHoverEvent *event); + virtual bool hoverLeaveEvent(QHoverEvent *event); + virtual bool hoverMoveEvent(QHoverEvent *event); + + virtual bool keyPressEvent(QKeyEvent *event); + virtual bool keyReleaseEvent(QKeyEvent *event); + + virtual bool mouseDoubleClickEvent(QMouseEvent *event); + virtual bool mouseMoveEvent(QMouseEvent *event); + virtual bool mousePressEvent(QMouseEvent *event); + virtual bool mouseReleaseEvent(QMouseEvent *event); + +#if QT_CONFIG(wheelevent) + virtual bool wheelEvent(QWheelEvent *event); +#endif +}; + +QT_END_NAMESPACE + +#endif // QWAYLANDQUICKSHELLINTEGRATION_H diff --git a/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp b/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp index 84e9c356d..110c3d0a3 100644 --- a/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp +++ b/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp @@ -208,6 +208,56 @@ void QWaylandQuickShellSurfaceItem::setAutoCreatePopupItems(bool enabled) emit autoCreatePopupItemsChanged(); } +void QWaylandQuickShellSurfaceItem::touchEvent(QTouchEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->touchEvent(event)) + QWaylandQuickItem::touchEvent(event); +} + +void QWaylandQuickShellSurfaceItem::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->hoverEnterEvent(event)) + QWaylandQuickItem::hoverEnterEvent(event); +} + +void QWaylandQuickShellSurfaceItem::hoverLeaveEvent(QHoverEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->hoverLeaveEvent(event)) + QWaylandQuickItem::hoverLeaveEvent(event); +} + +void QWaylandQuickShellSurfaceItem::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->hoverMoveEvent(event)) + QWaylandQuickItem::hoverMoveEvent(event); +} + + +void QWaylandQuickShellSurfaceItem::keyPressEvent(QKeyEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->keyPressEvent(event)) + QWaylandQuickItem::keyPressEvent(event); +} + +void QWaylandQuickShellSurfaceItem::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->keyReleaseEvent(event)) + QWaylandQuickItem::keyReleaseEvent(event); +} + +void QWaylandQuickShellSurfaceItem::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->mouseDoubleClickEvent(event)) + QWaylandQuickItem::mouseDoubleClickEvent(event); +} + void QWaylandQuickShellSurfaceItem::mouseMoveEvent(QMouseEvent *event) { Q_D(QWaylandQuickShellSurfaceItem); @@ -215,6 +265,13 @@ void QWaylandQuickShellSurfaceItem::mouseMoveEvent(QMouseEvent *event) QWaylandQuickItem::mouseMoveEvent(event); } +void QWaylandQuickShellSurfaceItem::mousePressEvent(QMouseEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->mousePressEvent(event)) + QWaylandQuickItem::mousePressEvent(event); +} + void QWaylandQuickShellSurfaceItem::mouseReleaseEvent(QMouseEvent *event) { Q_D(QWaylandQuickShellSurfaceItem); @@ -222,6 +279,15 @@ void QWaylandQuickShellSurfaceItem::mouseReleaseEvent(QMouseEvent *event) QWaylandQuickItem::mouseReleaseEvent(event); } +#if QT_CONFIG(wheelevent) +void QWaylandQuickShellSurfaceItem::wheelEvent(QWheelEvent *event) +{ + Q_D(QWaylandQuickShellSurfaceItem); + if (!d->m_shellIntegration->wheelEvent(event)) + QWaylandQuickItem::wheelEvent(event); +} +#endif + /*! \class QWaylandQuickShellEventFilter \brief QWaylandQuickShellEventFilter implements a Wayland popup grab diff --git a/src/compositor/extensions/qwaylandquickshellsurfaceitem.h b/src/compositor/extensions/qwaylandquickshellsurfaceitem.h index d14fa3fce..73a4900bb 100644 --- a/src/compositor/extensions/qwaylandquickshellsurfaceitem.h +++ b/src/compositor/extensions/qwaylandquickshellsurfaceitem.h @@ -76,8 +76,23 @@ Q_SIGNALS: protected: QWaylandQuickShellSurfaceItem(QWaylandQuickShellSurfaceItemPrivate &dd, QQuickItem *parent); + void touchEvent(QTouchEvent *event) override; + + void hoverEnterEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; + void hoverMoveEvent(QHoverEvent *event) override; + + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + + void mouseDoubleClickEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; + +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif }; QT_END_NAMESPACE diff --git a/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h b/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h index 7a458381e..f622368ab 100644 --- a/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h +++ b/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h @@ -41,6 +41,7 @@ #define QWAYLANDQUICKSHELLSURFACEITEM_P_H #include <QtWaylandCompositor/QWaylandQuickShellSurfaceItem> +#include <QtWaylandCompositor/QWaylandQuickShellIntegration> #include <QtWaylandCompositor/private/qwaylandquickitem_p.h> #include <QtCore/QBasicTimer> @@ -59,7 +60,6 @@ QT_BEGIN_NAMESPACE // We mean it. // -class QWaylandQuickShellIntegration; class QWaylandShellSurface; class QWaylandQuickShellSurfaceItem; @@ -74,16 +74,13 @@ public: QWaylandQuickShellIntegration *m_shellIntegration = nullptr; QWaylandShellSurface *m_shellSurface = nullptr; QQuickItem *m_moveItem = nullptr; - bool m_autoCreatePopupItems = false; -}; + bool m_autoCreatePopupItems = +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + true; +#else + false; +#endif -class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandQuickShellIntegration : public QObject -{ - Q_OBJECT -public: - QWaylandQuickShellIntegration(QObject *parent = nullptr) : QObject(parent) {} - virtual bool mouseMoveEvent(QMouseEvent *) { return false; } - virtual bool mouseReleaseEvent(QMouseEvent *) { return false; } }; class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandQuickShellEventFilter : public QObject diff --git a/src/compositor/extensions/qwaylandxdgshell.cpp b/src/compositor/extensions/qwaylandxdgshell.cpp index eb7b958b4..f06fd7934 100644 --- a/src/compositor/extensions/qwaylandxdgshell.cpp +++ b/src/compositor/extensions/qwaylandxdgshell.cpp @@ -1871,6 +1871,26 @@ uint QWaylandXdgPopup::sendConfigure(const QRect &geometry) } /*! + * \qmlmethod void QtWaylandCompositor::XdgPopup::sendPopupDone() + * \since 5.14 + * + * Dismiss the popup. According to the \c xdg-shell protocol this should make the + * client destroy the popup. + */ + +/*! + * \since 5.14 + * + * Dismiss the popup. According to the \c xdg-shell protocol this should make the + * client destroy the popup. + */ +void QWaylandXdgPopup::sendPopupDone() +{ + Q_D(QWaylandXdgPopup); + d->send_popup_done(); +} + +/*! * Returns the surface role for the QWaylandPopup. */ QWaylandSurfaceRole *QWaylandXdgPopup::role() diff --git a/src/compositor/extensions/qwaylandxdgshell.h b/src/compositor/extensions/qwaylandxdgshell.h index 774dd2282..f45038eb9 100644 --- a/src/compositor/extensions/qwaylandxdgshell.h +++ b/src/compositor/extensions/qwaylandxdgshell.h @@ -260,6 +260,7 @@ public: QPoint unconstrainedPosition() const; Q_INVOKABLE uint sendConfigure(const QRect &geometry); + Q_REVISION(14) Q_INVOKABLE void sendPopupDone(); static QWaylandSurfaceRole *role(); diff --git a/src/compositor/extensions/qwaylandxdgshellv5.cpp b/src/compositor/extensions/qwaylandxdgshellv5.cpp index 0628f55e7..a85efbc5f 100644 --- a/src/compositor/extensions/qwaylandxdgshellv5.cpp +++ b/src/compositor/extensions/qwaylandxdgshellv5.cpp @@ -115,9 +115,9 @@ QWaylandXdgPopupV5 *QWaylandXdgShellV5Private::topmostPopupForClient(wl_client * return clientPopups.empty() ? nullptr : clientPopups.last(); } -QWaylandXdgSurfaceV5 *QWaylandXdgShellV5Private::xdgSurfaceFromSurface(QWaylandSurface *surface) +QWaylandXdgSurfaceV5 *QWaylandXdgShellV5Private::xdgSurfaceFromSurface(QWaylandSurface *surface) const { - Q_FOREACH (QWaylandXdgSurfaceV5 *xdgSurface, m_xdgSurfaces) { + for (QWaylandXdgSurfaceV5 *xdgSurface : m_xdgSurfaces) { if (surface == xdgSurface->surface()) return xdgSurface; } diff --git a/src/compositor/extensions/qwaylandxdgshellv5_p.h b/src/compositor/extensions/qwaylandxdgshellv5_p.h index 8f5af746b..d10359e68 100644 --- a/src/compositor/extensions/qwaylandxdgshellv5_p.h +++ b/src/compositor/extensions/qwaylandxdgshellv5_p.h @@ -81,7 +81,7 @@ public: QMultiMap<struct wl_client *, QWaylandXdgSurfaceV5 *> m_xdgSurfaces; QMultiMap<struct wl_client *, QWaylandXdgPopupV5 *> m_xdgPopups; - QWaylandXdgSurfaceV5 *xdgSurfaceFromSurface(QWaylandSurface *surface); + QWaylandXdgSurfaceV5 *xdgSurfaceFromSurface(QWaylandSurface *surface) const; protected: void xdg_shell_destroy(Resource *resource) override; diff --git a/src/compositor/extensions/qwaylandxdgshellv6.cpp b/src/compositor/extensions/qwaylandxdgshellv6.cpp index f971fe5b4..d1ee45dab 100644 --- a/src/compositor/extensions/qwaylandxdgshellv6.cpp +++ b/src/compositor/extensions/qwaylandxdgshellv6.cpp @@ -1802,6 +1802,26 @@ uint QWaylandXdgPopupV6::sendConfigure(const QRect &geometry) } /*! + * \qmlmethod void QtWaylandCompositor::XdgPopupV6::sendPopupDone() + * \since 5.14 + * + * Dismiss the popup. According to the \c xdg-shell-unstable-v6 protocol this should make the + * client destroy the popup. + */ + +/*! + * \since 5.14 + * + * Dismiss the popup. According to the \c xdg-shell-unstable-v6 protocol this should make the + * client destroy the popup. + */ +void QWaylandXdgPopupV6::sendPopupDone() +{ + Q_D(QWaylandXdgPopupV6); + d->send_popup_done(); +} + +/*! * Returns the surface role for the QWaylandPopupV6. */ QWaylandSurfaceRole *QWaylandXdgPopupV6::role() diff --git a/src/compositor/extensions/qwaylandxdgshellv6.h b/src/compositor/extensions/qwaylandxdgshellv6.h index b9c47c578..64c82306c 100644 --- a/src/compositor/extensions/qwaylandxdgshellv6.h +++ b/src/compositor/extensions/qwaylandxdgshellv6.h @@ -246,6 +246,7 @@ public: QPoint unconstrainedPosition() const; Q_INVOKABLE uint sendConfigure(const QRect &geometry); + Q_REVISION(14) Q_INVOKABLE void sendPopupDone(); static QWaylandSurfaceRole *role(); diff --git a/src/compositor/extensions/qwltexturesharingextension.cpp b/src/compositor/extensions/qwltexturesharingextension.cpp new file mode 100644 index 000000000..1c15bb49f --- /dev/null +++ b/src/compositor/extensions/qwltexturesharingextension.cpp @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwltexturesharingextension_p.h" + +#include <QWaylandSurface> + +#include <QDebug> + +#include <QQuickWindow> + +#include <QPainter> +#include <QPen> +#include <QTimer> + +#include <QtGui/private/qtexturefilereader_p.h> +#include <QtGui/QOpenGLTexture> +#include <QtGui/QImageReader> + +#include <QtQuick/QSGTexture> +#include <QQmlContext> +#include <QThread> + +QT_BEGIN_NAMESPACE + +class SharedTexture : public QSGTexture +{ + Q_OBJECT +public: + SharedTexture(QtWayland::ServerBuffer *buffer); + + int textureId() const override; + QSize textureSize() const override; + bool hasAlphaChannel() const override; + bool hasMipmaps() const override; + + void bind() override; + +private: + void updateGLTexture() const; + QtWayland::ServerBuffer *m_buffer = nullptr; + mutable QOpenGLTexture *m_tex = nullptr; +}; + +SharedTexture::SharedTexture(QtWayland::ServerBuffer *buffer) + : m_buffer(buffer), m_tex(nullptr) +{ +} + +int SharedTexture::textureId() const +{ + updateGLTexture(); + return m_tex ? m_tex->textureId() : 0; +} + +QSize SharedTexture::textureSize() const +{ + updateGLTexture(); + return m_tex ? QSize(m_tex->width(), m_tex->height()) : QSize(); +} + +bool SharedTexture::hasAlphaChannel() const +{ + return true; +} + +bool SharedTexture::hasMipmaps() const +{ + updateGLTexture(); + return m_tex ? (m_tex->mipLevels() > 1) : false; +} + +void SharedTexture::bind() +{ + updateGLTexture(); + if (m_tex) + m_tex->bind(); +} + +inline void SharedTexture::updateGLTexture() const +{ + if (!m_tex && m_buffer) + m_tex = m_buffer->toOpenGlTexture(); +} + +class SharedTextureFactory : public QQuickTextureFactory +{ +public: + SharedTextureFactory(const QtWayland::ServerBuffer *buffer) + : m_buffer(buffer) + { + } + + ~SharedTextureFactory() override + { + if (m_buffer) + const_cast<QtWayland::ServerBuffer*>(m_buffer)->releaseOpenGlTexture(); + } + + QSize textureSize() const override + { + return m_buffer ? m_buffer->size() : QSize(); + } + + int textureByteCount() const override + { + return m_buffer ? (m_buffer->size().width() * m_buffer->size().height() * 4) : 0; + } + + QSGTexture *createTexture(QQuickWindow *) const override + { + return new SharedTexture(const_cast<QtWayland::ServerBuffer *>(m_buffer)); + } + +private: + const QtWayland::ServerBuffer *m_buffer = nullptr; +}; + +class SharedTextureImageResponse : public QQuickImageResponse +{ + Q_OBJECT +public: + SharedTextureImageResponse(QWaylandTextureSharingExtension *extension, const QString &id) + : m_id(id) + { + if (extension) + doRequest(extension); + } + + void doRequest(QWaylandTextureSharingExtension *extension) + { + m_extension = extension; + connect(extension, &QWaylandTextureSharingExtension::bufferResult, this, &SharedTextureImageResponse::doResponse); + QMetaObject::invokeMethod(extension, [this] { m_extension->requestBuffer(m_id); }, Qt::AutoConnection); + } + + QQuickTextureFactory *textureFactory() const override + { + if (m_buffer) { +// qDebug() << "Creating shared buffer texture for" << m_id; + return new SharedTextureFactory(m_buffer); + } +// qDebug() << "Shared buffer NOT found for" << m_id; + m_errorString = QLatin1Literal("Shared buffer not found"); + return nullptr; + } + + QString errorString() const override + { + return m_errorString; + } + +public slots: + void doResponse(const QString &key, QtWayland::ServerBuffer *buffer) + { + if (key != m_id) + return; //somebody else's texture + + m_buffer = buffer; + + if (m_extension) + disconnect(m_extension, &QWaylandTextureSharingExtension::bufferResult, this, &SharedTextureImageResponse::doResponse); + + emit finished(); + } + +private: + QString m_id; + QWaylandTextureSharingExtension *m_extension = nullptr; + mutable QString m_errorString; + QtWayland::ServerBuffer *m_buffer = nullptr; +}; + +QWaylandSharedTextureProvider::QWaylandSharedTextureProvider() +{ +} + +QWaylandSharedTextureProvider::~QWaylandSharedTextureProvider() +{ +} + +QQuickImageResponse *QWaylandSharedTextureProvider::requestImageResponse(const QString &id, const QSize &requestedSize) +{ + Q_UNUSED(requestedSize); + +// qDebug() << "Provider: got request for" << id; + + auto *extension = QWaylandTextureSharingExtension::self(); + auto *response = new SharedTextureImageResponse(extension, id); + if (!extension) + m_pendingResponses << response; + + return response; +} + +void QWaylandSharedTextureProvider::setExtensionReady(QWaylandTextureSharingExtension *extension) +{ + for (auto *response : qAsConst(m_pendingResponses)) + response->doRequest(extension); + m_pendingResponses.clear(); + m_pendingResponses.squeeze(); +} + +QWaylandTextureSharingExtension *QWaylandTextureSharingExtension::s_self = nullptr; // theoretical race conditions, but OK as long as we don't delete it while we are running + +QWaylandTextureSharingExtension::QWaylandTextureSharingExtension() +{ + s_self = this; +} + +QWaylandTextureSharingExtension::QWaylandTextureSharingExtension(QWaylandCompositor *compositor) + :QWaylandCompositorExtensionTemplate(compositor) +{ + s_self = this; +} + +QWaylandTextureSharingExtension::~QWaylandTextureSharingExtension() +{ + //qDebug() << Q_FUNC_INFO; + //dumpBufferInfo(); + + for (auto b : m_server_buffers) + delete b.buffer; + + if (s_self == this) + s_self = nullptr; +} + +void QWaylandTextureSharingExtension::setImageSearchPath(const QString &path) +{ + m_image_dirs = path.split(QLatin1Char(';')); + + for (auto it = m_image_dirs.begin(); it != m_image_dirs.end(); ++it) + if (!(*it).endsWith(QLatin1Char('/'))) + (*it) += QLatin1Char('/'); +} + +void QWaylandTextureSharingExtension::initialize() +{ + QWaylandCompositorExtensionTemplate::initialize(); + QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer()); + init(compositor->display(), 1); + + QString image_search_path = qEnvironmentVariable("QT_WAYLAND_SHAREDTEXTURE_SEARCH_PATH"); + if (!image_search_path.isEmpty()) + setImageSearchPath(image_search_path); + + if (m_image_dirs.isEmpty()) + m_image_dirs << QLatin1Literal(":/") << QLatin1Literal("./"); + + auto suffixes = QTextureFileReader::supportedFileFormats(); + suffixes.append(QImageReader::supportedImageFormats()); + for (auto ext : qAsConst(suffixes)) + m_image_suffixes << QLatin1Char('.') + QString::fromLatin1(ext); + + //qDebug() << "m_image_suffixes" << m_image_suffixes << "m_image_dirs" << m_image_dirs; + + auto *ctx = QQmlEngine::contextForObject(this); + if (ctx) { + QQmlEngine *engine = ctx->engine(); + if (engine) { + auto *provider = static_cast<QWaylandSharedTextureProvider*>(engine->imageProvider(QLatin1Literal("wlshared"))); + if (provider) + provider->setExtensionReady(this); + } + } +} + +QString QWaylandTextureSharingExtension::getExistingFilePath(const QString &key) const +{ + // The default search path blocks absolute pathnames, but this does not prevent relative + // paths containing '../'. We handle that here, at the price of also blocking directory + // names ending with two or more dots. + + if (key.contains(QLatin1Literal("../"))) + return QString(); + + for (auto dir : m_image_dirs) { + QString path = dir + key; + if (QFileInfo::exists(path)) + return path; + } + + for (auto dir : m_image_dirs) { + for (auto ext : m_image_suffixes) { + QString fp = dir + key + ext; + //qDebug() << "trying" << fp; + if (QFileInfo::exists(fp)) + return fp; + } + } + return QString(); +} + +QtWayland::ServerBuffer *QWaylandTextureSharingExtension::getBuffer(const QString &key) +{ + if (!initServerBufferIntegration()) + return nullptr; + +//qDebug() << "getBuffer" << key; + + QtWayland::ServerBuffer *buffer = nullptr; + + if ((buffer = m_server_buffers.value(key).buffer)) + return buffer; + + QByteArray pixelData; + QSize size; + uint glInternalFormat = GL_NONE; + + if (customPixelData(key, &pixelData, &size, &glInternalFormat)) { + if (!pixelData.isEmpty()) { + buffer = m_server_buffer_integration->createServerBufferFromData(pixelData, size, glInternalFormat); + if (!buffer) + qWarning() << "QWaylandTextureSharingExtension: could not create buffer from custom data for key:" << key; + } + } else { + QString pathName = getExistingFilePath(key); + //qDebug() << "pathName" << pathName; + if (pathName.isEmpty()) + return nullptr; + + buffer = getCompressedBuffer(pathName); + //qDebug() << "getCompressedBuffer" << buffer; + + if (!buffer) { + QImage img(pathName); + if (!img.isNull()) { + img = img.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + buffer = m_server_buffer_integration->createServerBufferFromImage(img, QtWayland::ServerBuffer::RGBA32); + } + //qDebug() << "createServerBufferFromImage" << buffer; + } + } + if (buffer) + m_server_buffers.insert(key, BufferInfo(buffer)); + + //qDebug() << ">>>>" << key << buffer; + + return buffer; +} + +// Compositor requesting image for its own UI +void QWaylandTextureSharingExtension::requestBuffer(const QString &key) +{ + //qDebug() << "requestBuffer" << key; + + if (thread() != QThread::currentThread()) + qWarning("QWaylandTextureSharingExtension::requestBuffer() called from outside main thread: possible race condition"); + + auto *buffer = getBuffer(key); + + if (buffer) + m_server_buffers[key].usedLocally = true; + + //dumpBufferInfo(); + + emit bufferResult(key, buffer); +} + +void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_request_image(Resource *resource, const QString &key) +{ + //qDebug() << "texture_sharing_request_image" << key; + auto *buffer = getBuffer(key); + if (buffer) { + struct ::wl_client *client = resource->client(); + struct ::wl_resource *buffer_resource = buffer->resourceForClient(client); + //qDebug() << " server_buffer resource" << buffer_resource; + if (buffer_resource) + send_provide_buffer(resource->handle, buffer_resource, key); + else + qWarning() << "QWaylandTextureSharingExtension: no buffer resource for client"; + } else { + send_image_failed(resource->handle, key, QString()); + } + //dumpBufferInfo(); +} + +void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_abandon_image(Resource *resource, const QString &key) +{ + Q_UNUSED(resource); + Q_UNUSED(key); +// qDebug() << Q_FUNC_INFO << resource << key; + QTimer::singleShot(100, this, &QWaylandTextureSharingExtension::cleanupBuffers); +} + +// A client has disconnected +void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); +// qDebug() << "texture_sharing_destroy_resource" << resource->handle << resource->handle->object.id << "client" << resource->client(); +// dumpBufferInfo(); + QTimer::singleShot(1000, this, &QWaylandTextureSharingExtension::cleanupBuffers); +} + +bool QWaylandTextureSharingExtension::initServerBufferIntegration() +{ + if (!m_server_buffer_integration) { + QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer()); + + m_server_buffer_integration = QWaylandCompositorPrivate::get(compositor)->serverBufferIntegration(); + if (!m_server_buffer_integration) { + qWarning("QWaylandTextureSharingExtension initialization failed: No Server Buffer Integration"); + if (qEnvironmentVariableIsEmpty("QT_WAYLAND_SERVER_BUFFER_INTEGRATION")) + qWarning("Set the environment variable 'QT_WAYLAND_SERVER_BUFFER_INTEGRATION' to specify."); + return false; + } + } + return true; +} + +QtWayland::ServerBuffer *QWaylandTextureSharingExtension::getCompressedBuffer(const QString &pathName) +{ + QFile f(pathName); + if (!f.open(QIODevice::ReadOnly)) + return nullptr; + + QTextureFileReader r(&f, pathName); + + if (!r.canRead()) + return nullptr; + + QTextureFileData td(r.read()); + + //qDebug() << "QWaylandTextureSharingExtension: reading compressed texture data" << td; + + if (!td.isValid()) { + qWarning() << "VulkanServerBufferIntegration:" << pathName << "not valid compressed texture"; + return nullptr; + } + + QByteArray pixelData = QByteArray::fromRawData(td.data().constData() + td.dataOffset(), td.dataLength()); + + return m_server_buffer_integration->createServerBufferFromData(pixelData, td.size(), td.glInternalFormat()); +} + +void QWaylandTextureSharingExtension::cleanupBuffers() +{ + for (auto it = m_server_buffers.begin(); it != m_server_buffers.end(); ) { + auto *buffer = it.value().buffer; + if (!it.value().usedLocally && !buffer->bufferInUse()) { + //qDebug() << "deleting buffer for" << it.key(); + it = m_server_buffers.erase(it); + delete buffer; + } else { + ++it; + } + } + //dumpBufferInfo(); +} + +void QWaylandTextureSharingExtension::dumpBufferInfo() +{ + qDebug() << "shared buffers:" << m_server_buffers.count(); + for (auto it = m_server_buffers.cbegin(); it != m_server_buffers.cend(); ++it) + qDebug() << " " << it.key() << ":" << it.value().buffer << "in use" << it.value().buffer->bufferInUse() << "usedLocally" << it.value().usedLocally ; +} + +QT_END_NAMESPACE + +#include "qwltexturesharingextension.moc" diff --git a/src/compositor/extensions/qwltexturesharingextension_p.h b/src/compositor/extensions/qwltexturesharingextension_p.h new file mode 100644 index 000000000..8f442a200 --- /dev/null +++ b/src/compositor/extensions/qwltexturesharingextension_p.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWLTEXTURESHARINGEXTENSION_P_H +#define QWLTEXTURESHARINGEXTENSION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "wayland-util.h" + +#include <QtCore/QMap> +#include <QtCore/QHash> + +#include <QtWaylandCompositor/QWaylandCompositorExtensionTemplate> +#include <QtWaylandCompositor/QWaylandQuickExtension> +#include <QtWaylandCompositor/QWaylandCompositor> + +#include <QQuickImageProvider> + +#include <QtWaylandCompositor/private/qwaylandcompositor_p.h> +#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h> + +#include <QtWaylandCompositor/private/qwayland-server-qt-texture-sharing-unstable-v1.h> + +QT_BEGIN_NAMESPACE + +namespace QtWayland +{ + class ServerBufferIntegration; +} + +class QWaylandTextureSharingExtension; +class SharedTextureImageResponse; + +class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandSharedTextureProvider : public QQuickAsyncImageProvider +{ +public: + QWaylandSharedTextureProvider(); + ~QWaylandSharedTextureProvider() override; + + QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + void setExtensionReady(QWaylandTextureSharingExtension *extension); + +private: + QVector<SharedTextureImageResponse*> m_pendingResponses; +}; + +class Q_WAYLAND_COMPOSITOR_EXPORT QWaylandTextureSharingExtension + : public QWaylandCompositorExtensionTemplate<QWaylandTextureSharingExtension> + , public QtWaylandServer::zqt_texture_sharing_v1 +{ + Q_OBJECT + Q_PROPERTY(QString imageSearchPath WRITE setImageSearchPath) +public: + QWaylandTextureSharingExtension(); + QWaylandTextureSharingExtension(QWaylandCompositor *compositor); + ~QWaylandTextureSharingExtension() override; + + void initialize() override; + + void setImageSearchPath(const QString &path); + + static QWaylandTextureSharingExtension *self() { return s_self; } + +public slots: + void requestBuffer(const QString &key); + +signals: + void bufferResult(const QString &key, QtWayland::ServerBuffer *buffer); + +protected slots: + void cleanupBuffers(); + +protected: + void zqt_texture_sharing_v1_request_image(Resource *resource, const QString &key) override; + void zqt_texture_sharing_v1_abandon_image(Resource *resource, const QString &key) override; + void zqt_texture_sharing_v1_destroy_resource(Resource *resource) override; + + virtual bool customPixelData(const QString &key, QByteArray *data, QSize *size, uint *glInternalFormat) + { + Q_UNUSED(key); + Q_UNUSED(data); + Q_UNUSED(size); + Q_UNUSED(glInternalFormat); + return false; + } + +private: + QtWayland::ServerBuffer *getBuffer(const QString &key); + bool initServerBufferIntegration(); + QtWayland::ServerBuffer *getCompressedBuffer(const QString &key); + QString getExistingFilePath(const QString &key) const; + void dumpBufferInfo(); + + struct BufferInfo + { + BufferInfo(QtWayland::ServerBuffer *b = nullptr) : buffer(b) {} + QtWayland::ServerBuffer *buffer = nullptr; + bool usedLocally = false; + }; + + QStringList m_image_dirs; + QStringList m_image_suffixes; + QHash<QString, BufferInfo> m_server_buffers; + QtWayland::ServerBufferIntegration *m_server_buffer_integration = nullptr; + + static QWaylandTextureSharingExtension *s_self; +}; + +Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(QWaylandTextureSharingExtension) + +QT_END_NAMESPACE + +#endif // QWLTEXTURESHARINGEXTENSION_P_H diff --git a/src/compositor/hardware_integration/qwlserverbufferintegration_p.h b/src/compositor/hardware_integration/qwlserverbufferintegration_p.h index 7d8901d58..e8f774e60 100644 --- a/src/compositor/hardware_integration/qwlserverbufferintegration_p.h +++ b/src/compositor/hardware_integration/qwlserverbufferintegration_p.h @@ -75,7 +75,8 @@ class Q_WAYLAND_COMPOSITOR_EXPORT ServerBuffer public: enum Format { RGBA32, - A8 + A8, + Custom }; ServerBuffer(const QSize &size, ServerBuffer::Format format); @@ -85,6 +86,7 @@ public: virtual bool bufferInUse() { return true; } virtual QOpenGLTexture *toOpenGlTexture() = 0; + virtual void releaseOpenGlTexture() {} virtual bool isYInverted() const; @@ -105,6 +107,13 @@ public: virtual bool supportsFormat(ServerBuffer::Format format) const = 0; virtual ServerBuffer *createServerBufferFromImage(const QImage &qimage, ServerBuffer::Format format) = 0; + virtual ServerBuffer *createServerBufferFromData(const QByteArray &data, const QSize &size, uint glInternalFormat) + { + Q_UNUSED(data); + Q_UNUSED(size); + Q_UNUSED(glInternalFormat); + return nullptr; + } }; } diff --git a/src/extensions/README.md b/src/extensions/README.md index f75e0f236..8024ffa80 100644 --- a/src/extensions/README.md +++ b/src/extensions/README.md @@ -3,12 +3,10 @@ The protocol extensions in this folder are considered implementation details of Qt. I.e. they may removed, renamed or changed without warning. -## Suffixed protocols - -For protocols that have a version suffix, however, we will strive to not break -backwards compatibility without bumping the suffix (renaming the protocol). -E.g.: If your client sees a `zqt_key_v1` global, it can safely bind to it: -the key event will always take the same number of arguments, regardless of +However, starting with Qt 5.4, we promise not to break backwards compatibility +without renaming (or removing) the protocol. I.e., if your client sees a global +from one of these extensions, it can safely bind to it: the existing events +and requests will always take the same number of arguments, regardless of compositor version. This is important also within a Qt-only scope if there are multiple versions of @@ -16,3 +14,11 @@ Qt on the system. Consider for instance an application statically linked to Qt (such as Qt Creator) running against a Qt compositor installed by the distro). In such cases we don't want the compositor and client to disagree on the protocol definition. + +## Protocol versioning. + +Protocol extensions in this folder should be versioned (e.g. `zqt_key_v1`). +If it is necessary to break protocol compatibility, they will be renamed by +incrementing the version number. For legacy reasons, there are also unversioned +protocols in this folder. Those protocols should be renamed to be versioned +if compatibility is broken. diff --git a/src/extensions/qt-texture-sharing-unstable-v1.xml b/src/extensions/qt-texture-sharing-unstable-v1.xml new file mode 100644 index 000000000..262ae487c --- /dev/null +++ b/src/extensions/qt-texture-sharing-unstable-v1.xml @@ -0,0 +1,57 @@ +<protocol name="qt_texture_sharing_unstable_v1"> + + <copyright> + Copyright (C) 2019 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + This file is part of the plugins of the Qt Toolkit. + + $QT_BEGIN_LICENSE:BSD$ + You may use this file under the terms of the BSD license as follows: + + "Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of The Qt Company Ltd nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + $QT_END_LICENSE$ + </copyright> + + <interface name="zqt_texture_sharing_v1" version="1"> + <request name="request_image"> + <arg name="key" type="string"/> + </request> + <request name="abandon_image"> + <arg name="key" type="string"/> + </request> + <event name="image_failed"> + <arg name="key" type="string"/> + <arg name="error_message" type="string"/> + </event> + <event name="provide_buffer"> + <arg name="buffer" type="object" interface="qt_server_buffer"/> + <arg name="key" type="string"/> + </event> + </interface> +</protocol> diff --git a/src/extensions/qt-vulkan-server-buffer-unstable-v1.xml b/src/extensions/qt-vulkan-server-buffer-unstable-v1.xml new file mode 100644 index 000000000..211d0a7c7 --- /dev/null +++ b/src/extensions/qt-vulkan-server-buffer-unstable-v1.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="qt_vulkan_server_buffer_unstable_v1"> + <copyright> + Copyright (C) 2019 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + This file is part of the plugins of the Qt Toolkit. + + $QT_BEGIN_LICENSE:BSD$ + You may use this file under the terms of the BSD license as follows: + + "Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of The Qt Company Ltd nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + + $QT_END_LICENSE$ + </copyright> + <interface name="zqt_vulkan_server_buffer_v1" version="1"> + <description summary="Internal protocol for buffer sharing using Vulkan external memory"> + This protocol is used internally by Qt for implementing the + qt_server_buffer extension on hardware that supports Vulkan external memory . + + This protocol is not part of the Qt API. It exists purely as an + implementation detail and may change from version to + version without notice, or even be removed. + </description> + <event name="server_buffer_created"> + <description summary="vulkan buffer information"> + Informs the client about a newly created server buffer. + The "fd" argument is a POSIX file descriptor representing the + underlying resources of a Vulkan device memory object as defined + in the GL_EXT_memory_object_fd extension. + </description> + <arg name="id" type="new_id" interface="qt_server_buffer"/> + <arg name="fd" type="fd" summary="GL_EXT_memory_object_fd"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + <arg name="memory_size" type="uint" summary="size in bytes"/> + <arg name="format" type="uint" summary="GL internal format"/> + </event> + </interface> +</protocol> diff --git a/src/hardwareintegration/client/vulkan-server/vulkan-server.pri b/src/hardwareintegration/client/vulkan-server/vulkan-server.pri new file mode 100644 index 000000000..2e13a87b7 --- /dev/null +++ b/src/hardwareintegration/client/vulkan-server/vulkan-server.pri @@ -0,0 +1,12 @@ +INCLUDEPATH += $$PWD + +QMAKE_USE += wayland-client + +SOURCES += \ + $$PWD/vulkanserverbufferintegration.cpp + +HEADERS += \ + $$PWD/vulkanserverbufferintegration.h + +CONFIG += wayland-scanner +WAYLANDCLIENTSOURCES += $$PWD/../../../extensions/qt-vulkan-server-buffer-unstable-v1.xml diff --git a/src/hardwareintegration/client/vulkan-server/vulkanserverbufferintegration.cpp b/src/hardwareintegration/client/vulkan-server/vulkanserverbufferintegration.cpp new file mode 100644 index 000000000..4b2be50e6 --- /dev/null +++ b/src/hardwareintegration/client/vulkan-server/vulkanserverbufferintegration.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "vulkanserverbufferintegration.h" +#include <QtWaylandClient/private/qwaylanddisplay_p.h> +#include <QDebug> +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLTexture> +#include <QtGui/qopengl.h> +#include <QtGui/QImage> +#include <QtCore/QCoreApplication> + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +static constexpr bool extraDebug = +#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG + true; +#else + false; +#endif + +#define DECL_GL_FUNCTION(name, type) \ + type name + +#define FIND_GL_FUNCTION(name, type) \ + do { \ + name = reinterpret_cast<type>(glContext->getProcAddress(#name)); \ + if (!name) { \ + qWarning() << "ERROR in GL proc lookup. Could not find " #name; \ + return false; \ + } \ + } while (0) + +struct VulkanServerBufferGlFunctions +{ + DECL_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC); + DECL_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC); + DECL_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC); + DECL_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC); + DECL_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC); + + bool init(QOpenGLContext *glContext) + { + FIND_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC); + FIND_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC); + FIND_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC); + FIND_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC); + FIND_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC); + + return true; + } + static bool create(QOpenGLContext *glContext); +}; + +static VulkanServerBufferGlFunctions *funcs = nullptr; + +bool VulkanServerBufferGlFunctions::create(QOpenGLContext *glContext) +{ + if (funcs) + return true; + funcs = new VulkanServerBufferGlFunctions; + if (!funcs->init(glContext)) { + delete funcs; + funcs = nullptr; + return false; + } + return true; +} + +VulkanServerBuffer::VulkanServerBuffer(VulkanServerBufferIntegration *integration, struct ::qt_server_buffer *id, + int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format) + : m_integration(integration) + , m_server_buffer(id) + , m_fd(fd) + , m_memorySize(memory_size) + , m_internalFormat(format) +{ + m_size = QSize(width, height); +} + +VulkanServerBuffer::~VulkanServerBuffer() +{ + if (QCoreApplication::closingDown()) + return; // can't trust anything at this point + + if (m_texture) { //only do gl cleanup if import has been called + m_integration->deleteGLTextureWhenPossible(m_texture); + + if (extraDebug) qDebug() << "glDeleteMemoryObjectsEXT" << m_memoryObject; + funcs->glDeleteMemoryObjectsEXT(1, &m_memoryObject); + } + qt_server_buffer_release(m_server_buffer); + qt_server_buffer_destroy(m_server_buffer); +} + +void VulkanServerBuffer::import() +{ + if (m_texture) + return; + + if (extraDebug) qDebug() << "importing" << m_fd << hex << glGetError(); + + auto *glContext = QOpenGLContext::currentContext(); + if (!glContext) + return; + + if (!funcs && !VulkanServerBufferGlFunctions::create(glContext)) + return; + + funcs->glCreateMemoryObjectsEXT(1, &m_memoryObject); + if (extraDebug) qDebug() << "glCreateMemoryObjectsEXT" << hex << glGetError(); + funcs->glImportMemoryFdEXT(m_memoryObject, m_memorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT, m_fd); + if (extraDebug) qDebug() << "glImportMemoryFdEXT" << hex << glGetError(); + + + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + m_texture->create(); + + if (extraDebug) qDebug() << "created texture" << m_texture->textureId() << hex << glGetError(); + + m_texture->bind(); + if (extraDebug) qDebug() << "bound texture" << hex << glGetError(); + funcs->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, m_internalFormat, m_size.width(), m_size.height(), m_memoryObject, 0 ); + if (extraDebug) qDebug() << "glTexStorageMem2DEXT" << hex << glGetError(); + if (extraDebug) qDebug() << "format" << hex << m_internalFormat << GL_RGBA8; +} + +QOpenGLTexture *VulkanServerBuffer::toOpenGlTexture() +{ + m_integration->deleteOrphanedTextures(); + if (!m_texture) + import(); + return m_texture; +} + +void VulkanServerBufferIntegration::initialize(QWaylandDisplay *display) +{ + m_display = display; + display->addRegistryListener(&wlDisplayHandleGlobal, this); +} + +QWaylandServerBuffer *VulkanServerBufferIntegration::serverBuffer(struct qt_server_buffer *buffer) +{ + return static_cast<QWaylandServerBuffer *>(qt_server_buffer_get_user_data(buffer)); +} + +void VulkanServerBufferIntegration::wlDisplayHandleGlobal(void *data, ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == "zqt_vulkan_server_buffer_v1") { + auto *integration = static_cast<VulkanServerBufferIntegration *>(data); + integration->QtWayland::zqt_vulkan_server_buffer_v1::init(registry, id, 1); + } +} + +void VulkanServerBufferIntegration::zqt_vulkan_server_buffer_v1_server_buffer_created(qt_server_buffer *id, int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format) +{ + if (extraDebug) qDebug() << "vulkan_server_buffer_server_buffer_created" << fd; + auto *server_buffer = new VulkanServerBuffer(this, id, fd, width, height, memory_size, format); + qt_server_buffer_set_user_data(id, server_buffer); +} + +void VulkanServerBufferIntegration::deleteOrphanedTextures() +{ + if (!QOpenGLContext::currentContext()) { + qWarning("VulkanServerBufferIntegration::deleteOrphanedTextures with no current context!"); + return; + } + qDeleteAll(orphanedTextures); + orphanedTextures.clear(); +} + +} + +QT_END_NAMESPACE diff --git a/src/hardwareintegration/client/vulkan-server/vulkanserverbufferintegration.h b/src/hardwareintegration/client/vulkan-server/vulkanserverbufferintegration.h new file mode 100644 index 000000000..7add74269 --- /dev/null +++ b/src/hardwareintegration/client/vulkan-server/vulkanserverbufferintegration.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VULKANSERVERBUFFERINTEGRATION_H +#define VULKANSERVERBUFFERINTEGRATION_H + +#include <QtWaylandClient/private/qwayland-wayland.h> +#include "qwayland-qt-vulkan-server-buffer-unstable-v1.h" +#include <QtWaylandClient/private/qwaylandserverbufferintegration_p.h> + +#include "vulkanserverbufferintegration.h" +#include <QtWaylandClient/private/qwaylanddisplay_p.h> +#include <QtCore/QTextStream> + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class VulkanServerBufferIntegration; + +class VulkanServerBuffer : public QWaylandServerBuffer +{ +public: + VulkanServerBuffer(VulkanServerBufferIntegration *integration, struct ::qt_server_buffer *id, int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format); + ~VulkanServerBuffer() override; + QOpenGLTexture* toOpenGlTexture() override; + +private: + void import(); + + VulkanServerBufferIntegration *m_integration = nullptr; + struct ::qt_server_buffer *m_server_buffer = nullptr; + QOpenGLTexture *m_texture = nullptr; + int m_fd = -1; + uint m_memorySize = 0; + uint m_internalFormat = 0; + GLuint m_memoryObject = 0; +}; + +class VulkanServerBufferIntegration + : public QWaylandServerBufferIntegration + , public QtWayland::zqt_vulkan_server_buffer_v1 +{ +public: + void initialize(QWaylandDisplay *display) override; + + QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) override; + + void deleteGLTextureWhenPossible(QOpenGLTexture *texture) { orphanedTextures << texture; } + void deleteOrphanedTextures(); + +protected: + void zqt_vulkan_server_buffer_v1_server_buffer_created(qt_server_buffer *id, int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format) override; + +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, + const QString &interface, uint32_t version); + QWaylandDisplay *m_display = nullptr; + QVector<QOpenGLTexture *> orphanedTextures; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp index 24dadff4d..030422c56 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp @@ -137,9 +137,8 @@ void QWaylandEglWindow::updateSurface(bool create) m_resize = true; } - } else if (create && wl_surface::isInitialized()) { - ::wl_surface *wlSurface = wl_surface::object(); - m_waylandEglWindow = wl_egl_window_create(wlSurface, sizeWithMargins.width(), sizeWithMargins.height()); + } else if (create && wlSurface()) { + m_waylandEglWindow = wl_egl_window_create(wlSurface(), sizeWithMargins.width(), sizeWithMargins.height()); } if (!m_eglSurface && m_waylandEglWindow && create) { diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp index bc1f74af9..5bd2760d0 100644 --- a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp +++ b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp @@ -192,7 +192,7 @@ public: } void blit(QWaylandEglWindow *window) { - Q_ASSERT(window->wl_surface::isInitialized()); + Q_ASSERT(window->wlSurface()); QOpenGLTextureCache *cache = QOpenGLTextureCache::cacheForContext(m_context->context()); QSize surfaceSize = window->surfaceSize(); diff --git a/src/hardwareintegration/compositor/vulkan-server/vulkan-server.pri b/src/hardwareintegration/compositor/vulkan-server/vulkan-server.pri new file mode 100644 index 000000000..63a96ad0f --- /dev/null +++ b/src/hardwareintegration/compositor/vulkan-server/vulkan-server.pri @@ -0,0 +1,16 @@ +INCLUDEPATH += $$PWD $$PWD/../../../3rdparty/util + +QT += vulkan_support-private + +QMAKE_USE_PRIVATE += wayland-server + +SOURCES += \ + $$PWD/vulkanserverbufferintegration.cpp \ + $$PWD/vulkanwrapper.cpp + +HEADERS += \ + $$PWD/vulkanserverbufferintegration.h \ + $$PWD/vulkanwrapper.h + +CONFIG += wayland-scanner +WAYLANDSERVERSOURCES += $$PWD/../../../extensions/qt-vulkan-server-buffer-unstable-v1.xml diff --git a/src/hardwareintegration/compositor/vulkan-server/vulkanserverbufferintegration.cpp b/src/hardwareintegration/compositor/vulkan-server/vulkanserverbufferintegration.cpp new file mode 100644 index 000000000..df197ca23 --- /dev/null +++ b/src/hardwareintegration/compositor/vulkan-server/vulkanserverbufferintegration.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "vulkanserverbufferintegration.h" + +#include "vulkanwrapper.h" + +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLTexture> +#include <QtGui/QOffscreenSurface> +#include <QtGui/qopengl.h> + +#include <unistd.h> +#include <fcntl.h> + +#include <QtCore/QDebug> + +QT_BEGIN_NAMESPACE +static constexpr bool extraDebug = false; + +#define DECL_GL_FUNCTION(name, type) \ + type name + +#define FIND_GL_FUNCTION(name, type) \ + do { \ + name = reinterpret_cast<type>(glContext->getProcAddress(#name)); \ + if (!name) { \ + qWarning() << "ERROR in GL proc lookup. Could not find " #name; \ + return false; \ + } \ + } while (0) + +struct VulkanServerBufferGlFunctions +{ + DECL_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC); + DECL_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC); + //DECL_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC); + DECL_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC); + DECL_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC); + + bool init(QOpenGLContext *glContext) + { + FIND_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC); + FIND_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC); + //FIND_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC); + FIND_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC); + FIND_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC); + + return true; + } + static bool create(QOpenGLContext *glContext); +}; + +static VulkanServerBufferGlFunctions *funcs = nullptr; + +//RAII +class CurrentContext +{ +public: + CurrentContext() + { + if (!QOpenGLContext::currentContext()) { + if (QOpenGLContext::globalShareContext()) { + if (!localContext) { + localContext = new QOpenGLContext; + localContext->setShareContext(QOpenGLContext::globalShareContext()); + localContext->create(); + } + if (!offscreenSurface) { + offscreenSurface = new QOffscreenSurface; + offscreenSurface->setFormat(localContext->format()); + offscreenSurface->create(); + } + localContext->makeCurrent(offscreenSurface); + localContextInUse = true; + } else { + qCritical("VulkanServerBufferIntegration: no globalShareContext"); + } + } + } + ~CurrentContext() + { + if (localContextInUse) + localContext->doneCurrent(); + } + QOpenGLContext *context() { return localContextInUse ? localContext : QOpenGLContext::currentContext(); } +private: + static QOpenGLContext *localContext; + static QOffscreenSurface *offscreenSurface; + bool localContextInUse = false; +}; + +QOpenGLContext *CurrentContext::localContext = nullptr; +QOffscreenSurface *CurrentContext::offscreenSurface = nullptr; + +bool VulkanServerBufferGlFunctions::create(QOpenGLContext *glContext) +{ + if (funcs) + return true; + funcs = new VulkanServerBufferGlFunctions; + if (!funcs->init(glContext)) { + delete funcs; + funcs = nullptr; + return false; + } + return true; +} + +VulkanServerBuffer::VulkanServerBuffer(VulkanServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format) + : QtWayland::ServerBuffer(qimage.size(),format) + , m_integration(integration) + , m_width(qimage.width()) + , m_height(qimage.height()) +{ + m_format = format; + switch (m_format) { + case RGBA32: + m_glInternalFormat = GL_RGBA8; + break; + // case A8: + // m_glInternalFormat = GL_R8; + // break; + default: + qWarning("VulkanServerBuffer: unsupported format"); + m_glInternalFormat = GL_RGBA8; + break; + } + + auto vulkanWrapper = m_integration->vulkanWrapper(); + m_vImage = vulkanWrapper->createTextureImage(qimage); + if (m_vImage) + m_fd = vulkanWrapper->getImageInfo(m_vImage, &m_memorySize); +} + +VulkanServerBuffer::VulkanServerBuffer(VulkanServerBufferIntegration *integration, VulkanImageWrapper *vImage, uint glInternalFormat, const QSize &size) + : QtWayland::ServerBuffer(size, QtWayland::ServerBuffer::Custom) + , m_integration(integration) + , m_width(size.width()) + , m_height(size.height()) + , m_vImage(vImage) + , m_glInternalFormat(glInternalFormat) +{ + auto vulkanWrapper = m_integration->vulkanWrapper(); + m_fd = vulkanWrapper->getImageInfo(m_vImage, &m_memorySize); +} + +VulkanServerBuffer::~VulkanServerBuffer() +{ + delete m_texture; //this is always nullptr for now + auto vulkanWrapper = m_integration->vulkanWrapper(); + vulkanWrapper->freeTextureImage(m_vImage); +} + +struct ::wl_resource *VulkanServerBuffer::resourceForClient(struct ::wl_client *client) +{ + auto *bufferResource = resourceMap().value(client); + if (!bufferResource) { + auto integrationResource = m_integration->resourceMap().value(client); + if (!integrationResource) { + qWarning("VulkanServerBuffer::resourceForClient: Trying to get resource for ServerBuffer. But client is not bound to the vulkan interface"); + return nullptr; + } + struct ::wl_resource *shm_integration_resource = integrationResource->handle; + Resource *resource = add(client, 1); + m_integration->send_server_buffer_created(shm_integration_resource, resource->handle, m_fd, m_width, m_height, m_memorySize, m_glInternalFormat); + return resource->handle; + } + return bufferResource->handle; +} + +QOpenGLTexture *VulkanServerBuffer::toOpenGlTexture() +{ + if (m_texture && m_texture->isCreated()) + return m_texture; + + CurrentContext current; + + if (!funcs && !VulkanServerBufferGlFunctions::create(current.context())) + return nullptr; + + funcs->glCreateMemoryObjectsEXT(1, &m_memoryObject); + if (extraDebug) qDebug() << "glCreateMemoryObjectsEXT" << hex << glGetError(); + + + int dupfd = fcntl(m_fd, F_DUPFD_CLOEXEC, 0); + if (dupfd < 0) { + perror("VulkanServerBuffer::toOpenGlTexture() Could not dup fd:"); + return nullptr; + } + + funcs->glImportMemoryFdEXT(m_memoryObject, m_memorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT, dupfd); + if (extraDebug) qDebug() << "glImportMemoryFdEXT" << hex << glGetError(); + + + if (!m_texture) + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + m_texture->create(); + + GLuint texId = m_texture->textureId(); + if (extraDebug) qDebug() << "created texture" << texId << hex << glGetError(); + + m_texture->bind(); + if (extraDebug) qDebug() << "bound texture" << texId << hex << glGetError(); + funcs->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, m_glInternalFormat, m_size.width(), m_size.height(), m_memoryObject, 0 ); + if (extraDebug) qDebug() << "glTexStorageMem2DEXT" << hex << glGetError(); + if (extraDebug) qDebug() << "format" << hex << m_glInternalFormat << GL_RGBA8; + + + return m_texture; +} + +void VulkanServerBuffer::releaseOpenGlTexture() +{ + if (!m_texture || !m_texture->isCreated()) + return; + + CurrentContext current; + m_texture->destroy(); + funcs->glDeleteMemoryObjectsEXT(1, &m_memoryObject); +} + + +bool VulkanServerBuffer::bufferInUse() +{ + return (m_texture && m_texture->isCreated()) || resourceMap().count() > 0; +} + +void VulkanServerBuffer::server_buffer_release(Resource *resource) +{ + qCDebug(qLcWaylandCompositorHardwareIntegration) << "server_buffer RELEASE resource" << resource->handle << wl_resource_get_id(resource->handle) << "for client" << resource->client(); + wl_resource_destroy(resource->handle); +} + +VulkanServerBufferIntegration::VulkanServerBufferIntegration() +{ +} + +VulkanServerBufferIntegration::~VulkanServerBufferIntegration() +{ +} + +void VulkanServerBufferIntegration::initializeHardware(QWaylandCompositor *compositor) +{ + Q_ASSERT(QGuiApplication::platformNativeInterface()); + + QtWaylandServer::zqt_vulkan_server_buffer_v1::init(compositor->display(), 1); +} + +bool VulkanServerBufferIntegration::supportsFormat(QtWayland::ServerBuffer::Format format) const +{ + switch (format) { + case QtWayland::ServerBuffer::RGBA32: + return true; + case QtWayland::ServerBuffer::A8: + return false; + default: + return false; + } +} + +QtWayland::ServerBuffer *VulkanServerBufferIntegration::createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) +{ + if (!m_vulkanWrapper) { + CurrentContext current; + m_vulkanWrapper = new VulkanWrapper(current.context()); + } + return new VulkanServerBuffer(this, qimage, format); +} + +QtWayland::ServerBuffer *VulkanServerBufferIntegration::createServerBufferFromData(const QByteArray &data, const QSize &size, uint glInternalFormat) +{ + if (!m_vulkanWrapper) { + CurrentContext current; + m_vulkanWrapper = new VulkanWrapper(current.context()); + } + + auto *vImage = m_vulkanWrapper->createTextureImageFromData(reinterpret_cast<const uchar*>(data.constData()), data.size(), size, glInternalFormat); + + if (vImage) + return new VulkanServerBuffer(this, vImage, glInternalFormat, size); + + qCWarning(qLcWaylandCompositorHardwareIntegration) << "could not load compressed texture"; + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/hardwareintegration/compositor/vulkan-server/vulkanserverbufferintegration.h b/src/hardwareintegration/compositor/vulkan-server/vulkanserverbufferintegration.h new file mode 100644 index 000000000..7246e36df --- /dev/null +++ b/src/hardwareintegration/compositor/vulkan-server/vulkanserverbufferintegration.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VULKANSERVERBUFFERINTEGRATION_H +#define VULKANSERVERBUFFERINTEGRATION_H + +#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h> + +#include "qwayland-server-qt-vulkan-server-buffer-unstable-v1.h" + +#include <QtGui/QImage> +#include <QtGui/QWindow> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtGui/QGuiApplication> + +#include <QtWaylandCompositor/qwaylandcompositor.h> +#include <QtWaylandCompositor/private/qwayland-server-server-buffer-extension.h> + +QT_BEGIN_NAMESPACE + +class VulkanServerBufferIntegration; +class VulkanWrapper; +struct VulkanImageWrapper; + +class VulkanServerBuffer : public QtWayland::ServerBuffer, public QtWaylandServer::qt_server_buffer +{ +public: + VulkanServerBuffer(VulkanServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format); + VulkanServerBuffer(VulkanServerBufferIntegration *integration, VulkanImageWrapper *vImage, uint glInternalFormat, const QSize &size); + ~VulkanServerBuffer() override; + + struct ::wl_resource *resourceForClient(struct ::wl_client *) override; + bool bufferInUse() override; + QOpenGLTexture *toOpenGlTexture() override; + void releaseOpenGlTexture() override; + +protected: + void server_buffer_release(Resource *resource) override; + +private: + VulkanServerBufferIntegration *m_integration = nullptr; + + int m_width; + int m_height; + int m_memorySize; + int m_fd = -1; + VulkanImageWrapper *m_vImage = nullptr; + QOpenGLTexture *m_texture = nullptr; + uint m_glInternalFormat; + GLuint m_memoryObject; +}; + +class VulkanServerBufferIntegration : + public QtWayland::ServerBufferIntegration, + public QtWaylandServer::zqt_vulkan_server_buffer_v1 +{ +public: + VulkanServerBufferIntegration(); + ~VulkanServerBufferIntegration() override; + + VulkanWrapper *vulkanWrapper() const { return m_vulkanWrapper; } + + void initializeHardware(QWaylandCompositor *) override; + + bool supportsFormat(QtWayland::ServerBuffer::Format format) const override; + QtWayland::ServerBuffer *createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) override; + QtWayland::ServerBuffer *createServerBufferFromData(const QByteArray &data, const QSize &size, uint glInternalFormat) override; + +private: + VulkanWrapper *m_vulkanWrapper = nullptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/hardwareintegration/compositor/vulkan-server/vulkanwrapper.cpp b/src/hardwareintegration/compositor/vulkan-server/vulkanwrapper.cpp new file mode 100644 index 000000000..771e1015c --- /dev/null +++ b/src/hardwareintegration/compositor/vulkan-server/vulkanwrapper.cpp @@ -0,0 +1,731 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// NOTE: Some of the code below is adapted from the public domain code at https://vulkan-tutorial.com/ + +#define GL_GLEXT_PROTOTYPES + +#include "vulkanwrapper.h" + +#include <QImage> +#include <QOpenGLContext> +#include <QtGui/qopengl.h> +#include <QtVulkanSupport/private/qvkconvenience_p.h> + +#include <set> + +#include <unistd.h> + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +static constexpr bool extraDebug = false; + +#define DECL_VK_FUNCTION(name) \ + PFN_ ## name name = nullptr; + +#define IMPL_VK_FUNCTION(name) \ + name = reinterpret_cast<PFN_ ## name>(f_glGetVkProcAddrNV(#name)); \ + if (!name) { \ + qCritical() << "ERROR in Vulkan proc lookup. Could not find " #name; \ + } + +struct QueueFamilyIndices { + int graphicsFamily = -1; + int presentFamily = -1; + + bool isComplete() { + return graphicsFamily >= 0 && presentFamily >= 0; + } +}; + +class VulkanWrapperPrivate +{ +public: + explicit VulkanWrapperPrivate(QOpenGLContext *glContext); + + VulkanImageWrapper *createTextureImage(const QImage &img); + VulkanImageWrapper *createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, VkFormat vkFormat); + + void freeTextureImage(VulkanImageWrapper *imageWrapper); + +private: + DECL_VK_FUNCTION(vkAllocateCommandBuffers); + DECL_VK_FUNCTION(vkAllocateMemory); + DECL_VK_FUNCTION(vkBeginCommandBuffer); + DECL_VK_FUNCTION(vkBindImageMemory); + DECL_VK_FUNCTION(vkCmdCopyBufferToImage); + DECL_VK_FUNCTION(vkCmdPipelineBarrier); + DECL_VK_FUNCTION(vkCreateImage); + DECL_VK_FUNCTION(vkDestroyImage); + DECL_VK_FUNCTION(vkDestroyBuffer); + DECL_VK_FUNCTION(vkEndCommandBuffer); + DECL_VK_FUNCTION(vkFreeCommandBuffers); + DECL_VK_FUNCTION(vkFreeMemory); + DECL_VK_FUNCTION(vkGetImageMemoryRequirements); + DECL_VK_FUNCTION(vkGetPhysicalDeviceMemoryProperties); + DECL_VK_FUNCTION(vkMapMemory); + DECL_VK_FUNCTION(vkQueueSubmit); + DECL_VK_FUNCTION(vkQueueWaitIdle); + DECL_VK_FUNCTION(vkUnmapMemory); + DECL_VK_FUNCTION(vkCreateBuffer); + DECL_VK_FUNCTION(vkGetBufferMemoryRequirements); + DECL_VK_FUNCTION(vkBindBufferMemory); + + DECL_VK_FUNCTION(vkCreateInstance); + DECL_VK_FUNCTION(vkEnumeratePhysicalDevices); + DECL_VK_FUNCTION(vkGetPhysicalDeviceProperties); + DECL_VK_FUNCTION(vkCreateDevice); + DECL_VK_FUNCTION(vkGetPhysicalDeviceFormatProperties); + + DECL_VK_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties); + DECL_VK_FUNCTION(vkCreateCommandPool); + + DECL_VK_FUNCTION(vkGetDeviceQueue); + DECL_VK_FUNCTION(vkGetImageMemoryRequirements2KHR); + DECL_VK_FUNCTION(vkGetMemoryFdKHR); + + //DECL_VK_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR); + + void initFunctions(PFNGLGETVKPROCADDRNVPROC f_glGetVkProcAddrNV) { + IMPL_VK_FUNCTION(vkAllocateCommandBuffers); + IMPL_VK_FUNCTION(vkAllocateMemory); + IMPL_VK_FUNCTION(vkBeginCommandBuffer); + IMPL_VK_FUNCTION(vkBindImageMemory); + IMPL_VK_FUNCTION(vkCmdCopyBufferToImage); + IMPL_VK_FUNCTION(vkCmdPipelineBarrier); + IMPL_VK_FUNCTION(vkCreateImage); + IMPL_VK_FUNCTION(vkDestroyImage); + IMPL_VK_FUNCTION(vkDestroyBuffer); + IMPL_VK_FUNCTION(vkEndCommandBuffer); + IMPL_VK_FUNCTION(vkFreeCommandBuffers); + IMPL_VK_FUNCTION(vkFreeMemory); + IMPL_VK_FUNCTION(vkGetImageMemoryRequirements); + IMPL_VK_FUNCTION(vkGetPhysicalDeviceMemoryProperties); + IMPL_VK_FUNCTION(vkMapMemory); + IMPL_VK_FUNCTION(vkQueueSubmit); + IMPL_VK_FUNCTION(vkQueueWaitIdle); + IMPL_VK_FUNCTION(vkUnmapMemory); + IMPL_VK_FUNCTION(vkCreateBuffer); + IMPL_VK_FUNCTION(vkGetBufferMemoryRequirements); + IMPL_VK_FUNCTION(vkBindBufferMemory); + + IMPL_VK_FUNCTION(vkCreateInstance); + IMPL_VK_FUNCTION(vkEnumeratePhysicalDevices); + IMPL_VK_FUNCTION(vkGetPhysicalDeviceProperties); + IMPL_VK_FUNCTION(vkCreateDevice); + IMPL_VK_FUNCTION(vkGetPhysicalDeviceFormatProperties); + + IMPL_VK_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties); + IMPL_VK_FUNCTION(vkCreateCommandPool); + + IMPL_VK_FUNCTION(vkGetDeviceQueue); + IMPL_VK_FUNCTION(vkGetImageMemoryRequirements2KHR); + IMPL_VK_FUNCTION(vkGetMemoryFdKHR); + + //IMPL_VK_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR); + } + + int findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); + + VulkanImageWrapper *createImage(VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, const QSize &size, int memSize); + bool transitionImageLayout(VkImage image, VkFormat /*format*/, VkImageLayout oldLayout, VkImageLayout newLayout); + bool createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory); + VkCommandBuffer beginSingleTimeCommands(); + void endSingleTimeCommands(VkCommandBuffer commandBuffer); + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height); + void createCommandPool(); + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device); + bool createLogicalDevice(); + +private: + VkInstance m_instance = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + VkCommandPool m_commandPool = VK_NULL_HANDLE; + + VkQueue m_graphicsQueue = VK_NULL_HANDLE; + + bool m_initFailed = false; +}; + +struct VulkanImageWrapper +{ + VkImage textureImage = VK_NULL_HANDLE; + int imgMemSize = -1; + QSize imgSize; + int imgFd = -1; + VkDeviceMemory textureImageMemory = VK_NULL_HANDLE; +}; + +int VulkanWrapperPrivate::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) +{ + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + qCritical("VulkanWrapper: failed to find suitable memory type!"); + return -1; +} + + +VulkanImageWrapper *VulkanWrapperPrivate::createImage(VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, const QSize &size, int memSize) +{ + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = size.width(); + imageInfo.extent.height = size.height(); + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkImage image = VK_NULL_HANDLE; + + if (vkCreateImage(m_device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + qCritical("VulkanWrapper: failed to create image!"); + return nullptr; + } + + QScopedPointer<VulkanImageWrapper> imageWrapper(new VulkanImageWrapper); + imageWrapper->textureImage = image; + imageWrapper->imgMemSize = memSize; + imageWrapper->imgSize = size; + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(m_device, image, &memRequirements); + + VkExportMemoryAllocateInfoKHR exportAllocInfo = {}; + exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR; + exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR; + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + int memoryType = findMemoryType(memRequirements.memoryTypeBits, properties); + if (memoryType < 0) + return nullptr; + allocInfo.memoryTypeIndex = memoryType; + allocInfo.pNext = &exportAllocInfo; + + if (vkAllocateMemory(m_device, &allocInfo, nullptr, &imageWrapper->textureImageMemory) != VK_SUCCESS) { + qCritical("VulkanWrapper: failed to allocate image memory!"); + return nullptr; + } + + int res = vkBindImageMemory(m_device, image, imageWrapper->textureImageMemory, 0); + Q_UNUSED(res); + if (extraDebug) qDebug() << "vkBindImageMemory res" << res; + + VkMemoryGetFdInfoKHR memoryFdInfo = {}; + memoryFdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR; + memoryFdInfo.memory = imageWrapper->textureImageMemory; + memoryFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR; + + res = vkGetMemoryFdKHR(m_device, &memoryFdInfo, &imageWrapper->imgFd); + if (extraDebug) qDebug() << "vkGetMemoryFdKHR res" << res << "fd" << imageWrapper->imgFd; + + return imageWrapper.take(); +} + + +bool VulkanWrapperPrivate::transitionImageLayout(VkImage image, VkFormat /*format*/, VkImageLayout oldLayout, VkImageLayout newLayout) +{ + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + qCritical("VulkanWrapper: unsupported layout transition!"); + return false; + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + return true; +} + +bool VulkanWrapperPrivate::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) +{ + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + qCritical("VulkanWrapper: failed to create buffer!"); + return false; + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(m_device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(m_device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + qCritical("VulkanWrapper: failed to allocate buffer memory!"); + return false; + } + + vkBindBufferMemory(m_device, buffer, bufferMemory, 0); + return true; +} + + +VkCommandBuffer VulkanWrapperPrivate::beginSingleTimeCommands() +{ + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = m_commandPool; + allocInfo.commandBufferCount = 1; + + if (extraDebug) qDebug() << "allocating..."; + + VkCommandBuffer commandBuffer; + int res = vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer); + Q_UNUSED(res); + if (extraDebug) qDebug() << "vkAllocateCommandBuffers res" << res; + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + res = vkBeginCommandBuffer(commandBuffer, &beginInfo); + if (extraDebug) qDebug() << "BEGIN res" << res; + + return commandBuffer; +} + +void VulkanWrapperPrivate::endSingleTimeCommands(VkCommandBuffer commandBuffer) +{ + int res = vkEndCommandBuffer(commandBuffer); + Q_UNUSED(res); + if (extraDebug) qDebug() << "END res" << res; + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(m_graphicsQueue); + + vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer); +} + +void VulkanWrapperPrivate::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) +{ + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); +} + +void VulkanWrapperPrivate::createCommandPool() +{ + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(m_physicalDevice); + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + + if (vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_commandPool) != VK_SUCCESS) { + m_initFailed = true; + qCritical("VulkanWrapperPrivate: could not create command pool"); + } +} + +QueueFamilyIndices VulkanWrapperPrivate::findQueueFamilies(VkPhysicalDevice device) +{ + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + if (extraDebug) qDebug() << "queueFamilyCount" << queueFamilyCount; + + + std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + +#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG + for (const auto& queueFamily : queueFamilies) { + qDebug() << "....q" << "count" << queueFamily.queueCount << queueFamily.timestampValidBits << hex << queueFamily.queueFlags; + } +#endif + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + break; + } + i++; + } + + return indices; +} + +bool VulkanWrapperPrivate::createLogicalDevice() +{ + QueueFamilyIndices indices = findQueueFamilies(m_physicalDevice); + + std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; + std::set<int> uniqueQueueFamilies = {indices.graphicsFamily}; //////, indices.presentFamily}; + + float queuePriority = 1.0f; + for (int queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures = {}; + + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + if (vkCreateDevice(m_physicalDevice, &createInfo, nullptr, &m_device) != VK_SUCCESS) { + qCritical("VulkanWrapper: failed to create logical device!"); + return false; + } + + vkGetDeviceQueue(m_device, indices.graphicsFamily, 0, &m_graphicsQueue); + return true; +} + +VulkanImageWrapper *VulkanWrapperPrivate::createTextureImage(const QImage &img) +{ + return createTextureImageFromData(img.constBits(), img.sizeInBytes(), img.size(), VK_FORMAT_R8G8B8A8_UNORM); +} + +VulkanImageWrapper *VulkanWrapperPrivate::createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, VkFormat vkFormat) +{ + if (m_initFailed) + return nullptr; + + int texWidth = size.width(); + int texHeight = size.height(); + bool ok; + if (extraDebug) qDebug("image load %p %dx%d", pixels, texWidth, texHeight); + if (!pixels) { + qCritical("VulkanWrapper: failed to load texture image!"); + return nullptr; + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + ok = createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + if (!ok) + return nullptr; + + void* data; + vkMapMemory(m_device, stagingBufferMemory, 0, bufferSize, 0, &data); + if (extraDebug) qDebug() << "mapped" << data << bufferSize; + memcpy(data, pixels, static_cast<size_t>(bufferSize)); + vkUnmapMemory(m_device, stagingBufferMemory); + + if (extraDebug) qDebug() << "creating image..."; + + QScopedPointer<VulkanImageWrapper> imageWrapper(createImage(vkFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, size, bufferSize)); + if (imageWrapper.isNull()) + return nullptr; + + if (extraDebug) qDebug() << "transition..."; + + const VkImage textureImage = imageWrapper->textureImage; + + ok = transitionImageLayout(textureImage, vkFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + if (!ok) + return nullptr; + + if (extraDebug) qDebug() << "copyBufferToImage..."; + copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight)); + transitionImageLayout(textureImage, vkFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(m_device, stagingBuffer, nullptr); + vkFreeMemory(m_device, stagingBufferMemory, nullptr); + + return imageWrapper.take(); +} + +void VulkanWrapperPrivate::freeTextureImage(VulkanImageWrapper *imageWrapper) +{ + if (!imageWrapper) + return; + + //"To avoid leaking resources, the application must release ownership of the file descriptor using the close system call" + ::close(imageWrapper->imgFd); + + // clean up the image memory + vkDestroyImage(m_device, imageWrapper->textureImage, nullptr); + vkFreeMemory(m_device, imageWrapper->textureImageMemory, nullptr); +} + +VulkanWrapperPrivate::VulkanWrapperPrivate(QOpenGLContext *glContext) +{ + if (extraDebug) qDebug("Creating Vulkan instance"); + VkApplicationInfo applicationInfo = {}; + applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + applicationInfo.pNext = nullptr; + applicationInfo.pApplicationName = nullptr; + applicationInfo.applicationVersion = 0; + applicationInfo.pEngineName = nullptr; + applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + applicationInfo.apiVersion = VK_MAKE_VERSION(1, 0, 5); + + VkInstanceCreateInfo instanceCreateInfo = {}; + instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instanceCreateInfo.pNext = nullptr; + instanceCreateInfo.flags = 0; + instanceCreateInfo.pApplicationInfo = &applicationInfo; + instanceCreateInfo.enabledLayerCount = 0; + instanceCreateInfo.ppEnabledLayerNames = nullptr; + instanceCreateInfo.enabledExtensionCount = 0; + instanceCreateInfo.ppEnabledExtensionNames = nullptr; + + auto f_glGetVkProcAddrNV = reinterpret_cast<PFNGLGETVKPROCADDRNVPROC>(glContext->getProcAddress("glGetVkProcAddrNV")); + + if (!f_glGetVkProcAddrNV) { + qCritical("VulkanWrapper: Could not find Vulkan/GL interop function glGetVkProcAddrNV"); + m_initFailed = true; + return; + } + + initFunctions(f_glGetVkProcAddrNV); + + VkResult instanceCreationResult = vkCreateInstance(&instanceCreateInfo, nullptr, &m_instance); + + if (extraDebug) qDebug() << "result" << instanceCreationResult; + + if (instanceCreationResult != VK_SUCCESS) { + qCritical() << "VulkanWrapper: Failed to create Vulkan instance: Error " + << instanceCreationResult; + m_initFailed = true; + return; + } + + uint32_t devCount; + + auto res = vkEnumeratePhysicalDevices(m_instance, &devCount, nullptr); + if (extraDebug) qDebug() << "vkEnumeratePhysicalDevices res =" << res << "count =" << devCount; + + QVarLengthArray<VkPhysicalDevice, 5> dev(devCount); + + res = vkEnumeratePhysicalDevices(m_instance, &devCount, dev.data()); + if (extraDebug) qDebug() << "...devs res =" << res << "count =" << devCount; + +#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG + VkPhysicalDeviceProperties props; + + vkGetPhysicalDeviceProperties(dev[0], &props); + + qDebug() << "Properties " << hex + << "apiVersion" << props.apiVersion + << "driverVersion" << props.driverVersion + << "vendorID" << props.vendorID + << "deviceID" << props.deviceID + << "deviceType" << props.deviceType + << "deviceName" << props.deviceName; +#endif + + m_physicalDevice = dev[0]; //TODO handle the case of multiple GPUs where only some support Vulkan + + bool ok = createLogicalDevice(); + if (!ok) { + qCritical("VulkanWrapperPrivate: could not create logical device"); + m_initFailed = true; + return; + } + + VkPhysicalDeviceMemoryProperties memProps; + + + vkGetPhysicalDeviceMemoryProperties(dev[0], &memProps); + +#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG + qDebug() << "Physical memory properties:\n" << "types:" << memProps.memoryTypeCount << "heaps:" << memProps.memoryHeapCount; + for (uint i = 0; i < memProps.memoryTypeCount; ++i) + qDebug() << " " << i << "heap" << memProps.memoryTypes[i].heapIndex << "flags" << hex << memProps.memoryTypes[i].propertyFlags; + + for (uint i = 0; i < memProps.memoryHeapCount; ++i) + qDebug() << " " << i << "size" << memProps.memoryHeaps[i].size << "flags" << hex << memProps.memoryHeaps[i].flags; +#endif + + int gpuMemoryType = -1; + + for (uint i = 0; i < memProps.memoryTypeCount; ++i) { + if (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { + gpuMemoryType = i; + break; + } + } + + if (gpuMemoryType < 0) { + qCritical("VulkanWrapper: Could not find GPU memory!"); + m_initFailed = true; + return; + } + +#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG + qDebug() << "GPU memory type:" << gpuMemoryType << "heap:" << memProps.memoryTypes[gpuMemoryType].heapIndex; + + for (int f = 0; f <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK; f++) + { + VkFormatProperties formatProps; + vkGetPhysicalDeviceFormatProperties(dev[0], VkFormat(f), &formatProps); + qDebug() << "format" << f << "features" << hex << formatProps.linearTilingFeatures << formatProps.optimalTilingFeatures << formatProps.bufferFeatures; + } +#endif + createCommandPool(); +} + + +VulkanWrapper::VulkanWrapper(QOpenGLContext *glContext) + : d_ptr(new VulkanWrapperPrivate(glContext)) +{ +} + +VulkanImageWrapper *VulkanWrapper::createTextureImage(const QImage &img) +{ + return d_ptr->createTextureImage(img); +} + +VulkanImageWrapper *VulkanWrapper::createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, uint glInternalFormat) +{ + VkFormat vkFormat = VkFormat(QVkConvenience::vkFormatFromGlFormat(glInternalFormat)); + if (vkFormat == VK_FORMAT_UNDEFINED) + return nullptr; + + return d_ptr->createTextureImageFromData(pixels, bufferSize, size, vkFormat); +} + +int VulkanWrapper::getImageInfo(const VulkanImageWrapper *imgWrapper, int *memSize, int *w, int *h) +{ + if (memSize) + *memSize = imgWrapper->imgMemSize; + if (w) + *w = imgWrapper->imgSize.width(); + if (h) + *h = imgWrapper->imgSize.height(); + return imgWrapper->imgFd; +} + +void VulkanWrapper::freeTextureImage(VulkanImageWrapper *imageWrapper) +{ + d_ptr->freeTextureImage(imageWrapper); +} + +QT_END_NAMESPACE diff --git a/src/hardwareintegration/compositor/vulkan-server/vulkanwrapper.h b/src/hardwareintegration/compositor/vulkan-server/vulkanwrapper.h new file mode 100644 index 000000000..541618fb3 --- /dev/null +++ b/src/hardwareintegration/compositor/vulkan-server/vulkanwrapper.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VULKANWRAPPER_H +#define VULKANWRAPPER_H + +#include <QOpenGLContext> + +QT_BEGIN_NAMESPACE + +class VulkanWrapper; +struct VulkanImageWrapper; +class VulkanWrapperPrivate; + +class QOpenGLContext; +class QImage; + +class VulkanWrapper +{ +public: + VulkanWrapper(QOpenGLContext *glContext); + + VulkanImageWrapper *createTextureImage(const QImage &img); + VulkanImageWrapper *createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, uint glInternalFormat); + int getImageInfo(const VulkanImageWrapper *imgWrapper, int *memSize, int *w = nullptr, int *h = nullptr); + void freeTextureImage(VulkanImageWrapper *imageWrapper); + +private: + VulkanWrapperPrivate *d_ptr; +}; + +QT_END_NAMESPACE + +#endif // VULKANWRAPPER_H diff --git a/src/imports/compositor/qwaylandquickcompositorplugin.cpp b/src/imports/compositor/qwaylandquickcompositorplugin.cpp index c7553d932..949d2cfaa 100644 --- a/src/imports/compositor/qwaylandquickcompositorplugin.cpp +++ b/src/imports/compositor/qwaylandquickcompositorplugin.cpp @@ -137,7 +137,9 @@ public: qmlRegisterType<QWaylandQuickCompositorQuickExtensionContainer>(uri, 1, 0, "WaylandCompositor"); qmlRegisterType<QWaylandQuickItem>(uri, 1, 0, "WaylandQuickItem"); qmlRegisterType<QWaylandQuickItem, 13>(uri, 1, 13, "WaylandQuickItem"); +#if QT_CONFIG(opengl) qmlRegisterType<QWaylandQuickHardwareLayer>(uri, 1, 2, "WaylandHardwareLayer"); +#endif qmlRegisterType<QWaylandMouseTracker>(uri, 1, 0, "WaylandMouseTracker"); qmlRegisterType<QWaylandQuickOutput>(uri, 1, 0, "WaylandOutput"); qmlRegisterType<QWaylandQuickSurface>(uri, 1, 0, "WaylandSurface"); diff --git a/src/imports/imports.pro b/src/imports/imports.pro index c57c95d20..1fced3dfd 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -1,3 +1,12 @@ TEMPLATE = subdirs -qtHaveModule(quick): SUBDIRS += compositor +qtHaveModule(quick): { + SUBDIRS += \ + compositor + + qtHaveModule(opengl): { + SUBDIRS += \ + texture-sharing \ + texture-sharing-extension + } +} diff --git a/src/imports/texture-sharing-extension/plugin.cpp b/src/imports/texture-sharing-extension/plugin.cpp new file mode 100644 index 000000000..42dcd8e2d --- /dev/null +++ b/src/imports/texture-sharing-extension/plugin.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqmlextensionplugin.h> +#include <QtQml/qqmlengine.h> + +#include "QtWaylandCompositor/private/qwltexturesharingextension_p.h" + +/*! + \qmlmodule QtWayland.Compositor.TextureSharingExtension 1 + \title Qt Wayland Shared Texture Provider + \ingroup qmlmodules + \brief Adds a mechanism to share GPU memory + + \section2 Summary + + This module lets the compositor export graphical resources that can be used by clients, + without allocating any graphics memory in the client. + + \section2 Usage + + This module is imported like this: + + \code + import QtWayland.Compositor.TextureSharingExtension 1.0 + \endcode + + To use this module in a compositor, instantiate the extension object as a child of the compositor object, like this: + + + \code + WaylandCompositor { + //... + TextureSharingExtension { + } + } + \endcode + + The sharing functionality is provided through a QQuickImageProvider. Use + the "image:" scheme for the URL source of the image, followed by the + identifier \e wlshared, followed by the image file path. For example: + + \code + Image { source: "image://wlshared/wallpapers/mybackground.jpg" } + \endcode + +*/ + +QT_BEGIN_NAMESPACE + +class QWaylandTextureSharingExtensionPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) +public: + QWaylandTextureSharingExtensionPlugin(QObject *parent = nullptr) : QQmlExtensionPlugin(parent) {} + + void registerTypes(const char *uri) override + { + Q_ASSERT(uri == QStringLiteral("QtWayland.Compositor.TextureSharingExtension")); + qmlRegisterType<QWaylandTextureSharingExtensionQuickExtension>("QtWayland.Compositor.TextureSharingExtension", 1, 0, "TextureSharingExtension"); + } + + void initializeEngine(QQmlEngine *engine, const char *uri) override + { + Q_UNUSED(uri); + engine->addImageProvider("wlshared", new QWaylandSharedTextureProvider); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/texture-sharing-extension/qmldir b/src/imports/texture-sharing-extension/qmldir new file mode 100644 index 000000000..182e5c0ee --- /dev/null +++ b/src/imports/texture-sharing-extension/qmldir @@ -0,0 +1,3 @@ +module QtWayland.Compositor.TextureSharingExtension +plugin qwaylandtexturesharingextension +classname QWaylandTextureSharingExtensionPlugin diff --git a/src/imports/texture-sharing-extension/texture-sharing-extension.pro b/src/imports/texture-sharing-extension/texture-sharing-extension.pro new file mode 100644 index 000000000..68a8cf757 --- /dev/null +++ b/src/imports/texture-sharing-extension/texture-sharing-extension.pro @@ -0,0 +1,11 @@ +CXX_MODULE = qml +TARGET = qwaylandtexturesharingextension +TARGETPATH = QtWayland/Compositor/TextureSharingExtension +IMPORT_VERSION = 1.$$QT_MINOR_VERSION + +SOURCES += \ + plugin.cpp + +QT += quick-private qml gui-private core-private waylandcompositor waylandcompositor-private + +load(qml_plugin) diff --git a/src/imports/texture-sharing/plugin.cpp b/src/imports/texture-sharing/plugin.cpp new file mode 100644 index 000000000..9cf6bbca1 --- /dev/null +++ b/src/imports/texture-sharing/plugin.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWaylandClient module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqmlextensionplugin.h> +#include <QtQml/qqmlengine.h> + +#include "sharedtextureprovider.h" + +/*! + \qmlmodule QtWayland.Client.TextureSharing 1 + \title Qt Wayland Shared Texture Provider + \ingroup qmlmodules + \brief Adds an image provider which utilizes shared GPU memory + + \section2 Summary + + This module allows Qt Wayland clients to use graphical resources exported + by the compositor, without allocating any graphics memory in the client. + \section2 Usage + + To use this module, import it like this: + \code + import QtWayland.Client.TextureSharing 1.0 + \endcode + + The sharing functionality is provided through a QQuickImageProvider. Use + the "image:" scheme for the URL source of the image, followed by the + identifier \e wlshared, followed by the image file path. For example: + + \code + Image { source: "image://wlshared/wallpapers/mybackground.jpg" } + \endcode + + The shared texture module does not provide any directly usable QML types. +*/ + +QT_BEGIN_NAMESPACE + +class QWaylandTextureSharingPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) +public: + QWaylandTextureSharingPlugin(QObject *parent = nullptr) : QQmlExtensionPlugin(parent) {} + + void registerTypes(const char *uri) override + { + Q_ASSERT(uri == QStringLiteral("QtWayland.Client.TextureSharing")); + qmlRegisterModule(uri, 1, 0); + } + + void initializeEngine(QQmlEngine *engine, const char *uri) override + { + Q_UNUSED(uri); + engine->addImageProvider("wlshared", new SharedTextureProvider); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/texture-sharing/qmldir b/src/imports/texture-sharing/qmldir new file mode 100644 index 000000000..cf3b74c48 --- /dev/null +++ b/src/imports/texture-sharing/qmldir @@ -0,0 +1,3 @@ +module QtWayland.Client.TextureSharing +plugin qwaylandtexturesharing +classname QWaylandTextureSharingPlugin diff --git a/src/imports/texture-sharing/sharedtextureprovider.cpp b/src/imports/texture-sharing/sharedtextureprovider.cpp new file mode 100644 index 000000000..707e94ae6 --- /dev/null +++ b/src/imports/texture-sharing/sharedtextureprovider.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "sharedtextureprovider.h" + +#include <QFile> +#include <QDebug> +#include <qopenglfunctions.h> +#include <QQuickWindow> + +#include <QtWaylandClient/private/qwaylandintegration_p.h> +#include <QtWaylandClient/private/qwaylandserverbufferintegration_p.h> +#include <QtGui/QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtGui/QWindow> +#include <QOpenGLTexture> +#include <QImageReader> + +#include <QTimer> + +#include "texturesharingextension.h" + +QT_BEGIN_NAMESPACE + +SharedTexture::SharedTexture(QtWaylandClient::QWaylandServerBuffer *buffer) + : m_buffer(buffer), m_tex(nullptr) +{ +} + +int SharedTexture::textureId() const +{ + updateGLTexture(); + return m_tex ? m_tex->textureId() : 0; +} + +QSize SharedTexture::textureSize() const +{ + updateGLTexture(); + return m_tex ? QSize(m_tex->width(), m_tex->height()) : QSize(); +} + +bool SharedTexture::hasAlphaChannel() const +{ + return true; +} + +bool SharedTexture::hasMipmaps() const +{ + updateGLTexture(); + return m_tex ? (m_tex->mipLevels() > 1) : false; +} + +void SharedTexture::bind() +{ + updateGLTexture(); + if (m_tex) + m_tex->bind(); +} + +inline void SharedTexture::updateGLTexture() const +{ + if (!m_tex && m_buffer) + m_tex = m_buffer->toOpenGlTexture(); +} + +class SharedTextureFactory : public QQuickTextureFactory +{ +public: + SharedTextureFactory(const QtWaylandClient::QWaylandServerBuffer *buffer, const QString &id, SharedTextureRegistry *registry) + : m_buffer(buffer), m_id(id), m_registry(registry) + { + } + + ~SharedTextureFactory() override + { + //qDebug() << "====> DESTRUCTOR SharedTextureFactory" << this; + if (m_registry) + m_registry->abandonBuffer(m_id); + delete m_buffer; // TODO: make sure we are not keeping references to this elsewhere + //qDebug() << "buffer deleted"; + } + + QSize textureSize() const override + { + return m_buffer ? m_buffer->size() : QSize(); + } + + int textureByteCount() const override + { + return m_buffer ? (m_buffer->size().width() * m_buffer->size().height() * 4) : 0; + } + + QSGTexture *createTexture(QQuickWindow *) const override + { + return new SharedTexture(const_cast<QtWaylandClient::QWaylandServerBuffer *>(m_buffer)); + } + +private: + const QtWaylandClient::QWaylandServerBuffer *m_buffer = nullptr; + QString m_id; + QPointer<SharedTextureRegistry> m_registry; +}; + + +SharedTextureRegistry::SharedTextureRegistry() + : m_extension(new TextureSharingExtension) +{ + connect(m_extension, &TextureSharingExtension::bufferReceived, this, &SharedTextureRegistry::receiveBuffer); + connect(m_extension, &TextureSharingExtension::activeChanged, this, &SharedTextureRegistry::handleExtensionActive); +} + +SharedTextureRegistry::~SharedTextureRegistry() +{ + delete m_extension; +} + +const QtWaylandClient::QWaylandServerBuffer *SharedTextureRegistry::bufferForId(const QString &id) const +{ + return m_buffers.value(id); +} + +void SharedTextureRegistry::requestBuffer(const QString &id) +{ + if (!m_extension->isActive()) { + //qDebug() << "Extension not active, adding" << id << "to queue"; + m_pendingBuffers << id; + return; + } + m_extension->requestImage(id); +} + +void SharedTextureRegistry::abandonBuffer(const QString &id) +{ + m_buffers.remove(id); + m_extension->abandonImage(id); +} + + +void SharedTextureRegistry::handleExtensionActive() +{ + //qDebug() << "handleExtensionActive, queue:" << m_pendingBuffers; + if (m_extension->isActive()) + while (!m_pendingBuffers.isEmpty()) + requestBuffer(m_pendingBuffers.takeFirst()); +} + +bool SharedTextureRegistry::preinitialize() +{ + auto *serverBufferIntegration = QGuiApplicationPrivate::platformIntegration()->nativeInterface()->nativeResourceForIntegration("server_buffer_integration"); + + if (!serverBufferIntegration) { + qWarning() << "Wayland Server Buffer Integration not available."; + return false; + } + + return true; +} + +void SharedTextureRegistry::receiveBuffer(QtWaylandClient::QWaylandServerBuffer *buffer, const QString& id) +{ + //qDebug() << "ReceiveBuffer for id" << id; + if (buffer) + m_buffers.insert(id, buffer); + emit replyReceived(id); +} + +class SharedTextureImageResponse : public QQuickImageResponse +{ + Q_OBJECT +public: + SharedTextureImageResponse(SharedTextureRegistry *registry, const QString &id) + : m_id(id), m_registry(registry) + { + if (!m_registry || m_registry->bufferForId(id)) { + // Shortcut: no server roundtrip needed, just let the event loop call the slot + QMetaObject::invokeMethod(this, "doResponse", Qt::QueuedConnection, Q_ARG(QString, id)); + + } else { + // TBD: timeout? + connect(registry, &SharedTextureRegistry::replyReceived, this, &SharedTextureImageResponse::doResponse); + registry->requestBuffer(id); + } + } + + QQuickTextureFactory *textureFactory() const override + { + if (m_registry) { + const QtWaylandClient::QWaylandServerBuffer *buffer = m_registry->bufferForId(m_id); + if (buffer) { + //qDebug() << "Creating shared buffer texture for" << m_id; + return new SharedTextureFactory(buffer, m_id, m_registry); + } + //qDebug() << "Shared buffer NOT found for" << m_id; + } + + // No shared buffer, do fallback + QString fbPath = fallbackPath(); + if (fbPath.isEmpty()) { + m_errorString = QStringLiteral("Shared buffer not found, and no fallback path set."); + return nullptr; + } + + QImageReader reader(fbPath + m_id); + QImage img = reader.read(); + if (img.isNull()) { + qWarning() << "Could not load local image from id/path" << reader.fileName(); + m_errorString = QStringLiteral("Shared buffer not found, and fallback local file loading failed: ") + reader.errorString(); + return nullptr; + } + return QQuickTextureFactory::textureFactoryForImage(img); + } + + QString errorString() const override + { + return m_errorString; + } + + static QString fallbackPath() + { + static QString fbPath; + static bool isInit = false; + if (!isInit) { + isInit = true; + QByteArray envVal = qgetenv("QT_SHAREDTEXTURE_FALLBACK_DIR"); + if (!envVal.isEmpty()) { + fbPath = QString::fromLocal8Bit(envVal); + if (!fbPath.endsWith(QLatin1Char('/'))) + fbPath.append(QLatin1Char('/')); + } + } + return fbPath; + } + + +public slots: + void doResponse(const QString &key) { + if (key != m_id) + return; // not our buffer + + // No need to be called again + if (m_registry) + disconnect(m_registry, &SharedTextureRegistry::replyReceived, this, &SharedTextureImageResponse::doResponse); + + emit finished(); + } + +private: + QString m_id; + SharedTextureRegistry *m_registry = nullptr; + mutable QString m_errorString; +}; + + +SharedTextureProvider::SharedTextureProvider() +{ + m_sharingAvailable = SharedTextureRegistry::preinitialize(); + if (!m_sharingAvailable) { + if (SharedTextureImageResponse::fallbackPath().isEmpty()) + qWarning() << "Shared buffer images not available, and no fallback directory set."; + else + qWarning() << "Shared buffer images not available, will fallback to local image files from" << SharedTextureImageResponse::fallbackPath(); + } +} + +SharedTextureProvider::~SharedTextureProvider() +{ + delete m_registry; +} + +QQuickImageResponse *SharedTextureProvider::requestImageResponse(const QString &id, const QSize &requestedSize) +{ + Q_UNUSED(requestedSize); + + //qDebug() << "Provider: got request for" << id; + + if (m_sharingAvailable && !m_registry) + m_registry = new SharedTextureRegistry; + + return new SharedTextureImageResponse(m_registry, id); +} + +QT_END_NAMESPACE + +#include "sharedtextureprovider.moc" diff --git a/src/imports/texture-sharing/sharedtextureprovider.h b/src/imports/texture-sharing/sharedtextureprovider.h new file mode 100644 index 000000000..f25c7de9c --- /dev/null +++ b/src/imports/texture-sharing/sharedtextureprovider.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SHAREDTEXTUREPROVIDER_H +#define SHAREDTEXTUREPROVIDER_H + +#include <QOpenGLFunctions> +#include <QQuickImageProvider> +#include <QtQuick/QSGTexture> +#include <QScopedPointer> +#include <QHash> + +#include <QtWaylandClient/private/qwaylandserverbufferintegration_p.h> + +QT_BEGIN_NAMESPACE + +class TextureSharingExtension; + +class SharedTextureRegistry : public QObject +{ + Q_OBJECT +public: + SharedTextureRegistry(); + ~SharedTextureRegistry() override; + + const QtWaylandClient::QWaylandServerBuffer *bufferForId(const QString &id) const; + void requestBuffer(const QString &id); + void abandonBuffer(const QString &id); + + static bool preinitialize(); + +public slots: + void receiveBuffer(QtWaylandClient::QWaylandServerBuffer *buffer, const QString &id); + +signals: + void replyReceived(const QString &id); + +private slots: + void handleExtensionActive(); + +private: + TextureSharingExtension *m_extension = nullptr; + QHash<QString, QtWaylandClient::QWaylandServerBuffer *> m_buffers; + QStringList m_pendingBuffers; +}; + +class SharedTextureProvider : public QQuickAsyncImageProvider +{ +public: + SharedTextureProvider(); + ~SharedTextureProvider() override; + + QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + +private: + SharedTextureRegistry *m_registry = nullptr; + bool m_sharingAvailable = false; +}; + +class SharedTexture : public QSGTexture +{ + Q_OBJECT +public: + SharedTexture(QtWaylandClient::QWaylandServerBuffer *buffer); + + int textureId() const override; + QSize textureSize() const override; + bool hasAlphaChannel() const override; + bool hasMipmaps() const override; + + void bind() override; + +private: + void updateGLTexture() const; + QtWaylandClient::QWaylandServerBuffer *m_buffer = nullptr; + mutable QOpenGLTexture *m_tex = nullptr; +}; + + +QT_END_NAMESPACE + +#endif // SHAREDTEXTUREPROVIDER_H diff --git a/src/imports/texture-sharing/texture-sharing.pro b/src/imports/texture-sharing/texture-sharing.pro new file mode 100644 index 000000000..bec769ecb --- /dev/null +++ b/src/imports/texture-sharing/texture-sharing.pro @@ -0,0 +1,21 @@ +CXX_MODULE = qml +TARGET = qwaylandtexturesharing +TARGETPATH = QtWayland/Client/TextureSharing +IMPORT_VERSION = 1.$$QT_MINOR_VERSION + +HEADERS += \ + sharedtextureprovider.h \ + texturesharingextension.h + +SOURCES += \ + plugin.cpp \ + sharedtextureprovider.cpp \ + texturesharingextension.cpp + +QT += quick-private qml gui-private core-private waylandclient waylandclient-private +CONFIG += wayland-scanner + +WAYLANDCLIENTSOURCES += ../../extensions/qt-texture-sharing-unstable-v1.xml + + +load(qml_plugin) diff --git a/src/imports/texture-sharing/texturesharingextension.cpp b/src/imports/texture-sharing/texturesharingextension.cpp new file mode 100644 index 000000000..31106d694 --- /dev/null +++ b/src/imports/texture-sharing/texturesharingextension.cpp @@ -0,0 +1,86 @@ + +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "texturesharingextension.h" +#include <QtWaylandClient/private/qwaylanddisplay_p.h> +#include <QtWaylandClient/private/qwaylandintegration_p.h> +#include <QtWaylandClient/private/qwaylandserverbufferintegration_p.h> +#include <QtGui/QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/QWindow> +#include <QtGui/QPlatformSurfaceEvent> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +TextureSharingExtension::TextureSharingExtension() + : QWaylandClientExtensionTemplate(/* Supported protocol version */ 1 ) +{ + auto *wayland_integration = static_cast<QtWaylandClient::QWaylandIntegration *>(QGuiApplicationPrivate::platformIntegration()); + m_server_buffer_integration = wayland_integration->serverBufferIntegration(); + if (!m_server_buffer_integration) { + qCritical() << "This application requires a working serverBufferIntegration"; + QGuiApplication::quit(); + } +} + +void TextureSharingExtension::zqt_texture_sharing_v1_provide_buffer(struct ::qt_server_buffer *buffer, const QString &key) +{ + QtWaylandClient::QWaylandServerBuffer *serverBuffer = m_server_buffer_integration->serverBuffer(buffer); + emit bufferReceived(serverBuffer, key); +} + +void TextureSharingExtension::zqt_texture_sharing_v1_image_failed(const QString &key, const QString &message) +{ + qWarning() << "TextureSharingExtension" << key << "not found" << message; + emit bufferReceived(nullptr, key); +} +void TextureSharingExtension::requestImage(const QString &key) +{ + request_image(key); +} + +void TextureSharingExtension::abandonImage(const QString &key) +{ + abandon_image(key); +} + +QT_END_NAMESPACE diff --git a/src/imports/texture-sharing/texturesharingextension.h b/src/imports/texture-sharing/texturesharingextension.h new file mode 100644 index 000000000..7b864fbc8 --- /dev/null +++ b/src/imports/texture-sharing/texturesharingextension.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TEXTURESHARINGEXTENSION_H +#define TEXTURESHARINGEXTENSION_H + +#include <qpa/qwindowsysteminterface.h> +#include <QtWaylandClient/private/qwayland-wayland.h> +#include <QtWaylandClient/qwaylandclientextension.h> +#include "qwayland-qt-texture-sharing-unstable-v1.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + class QWaylandServerBuffer; + class QWaylandServerBufferIntegration; +}; + +class TextureSharingExtension : public QWaylandClientExtensionTemplate<TextureSharingExtension> + , public QtWayland::zqt_texture_sharing_v1 +{ + Q_OBJECT +public: + TextureSharingExtension(); + +public slots: + void requestImage(const QString &key); + void abandonImage(const QString &key); + +signals: + void bufferReceived(QtWaylandClient::QWaylandServerBuffer *buffer, const QString &key); + +private: + void zqt_texture_sharing_v1_provide_buffer(struct ::qt_server_buffer *buffer, const QString &key) override; + void zqt_texture_sharing_v1_image_failed(const QString &key, const QString &message) override; + QtWaylandClient::QWaylandServerBufferIntegration *m_server_buffer_integration = nullptr; +}; + +QT_END_NAMESPACE + +#endif // TEXTURESHARINGEXTENSION_H diff --git a/src/plugins/hardwareintegration/client/client.pro b/src/plugins/hardwareintegration/client/client.pro index 82e431ee8..7b7e8a49a 100644 --- a/src/plugins/hardwareintegration/client/client.pro +++ b/src/plugins/hardwareintegration/client/client.pro @@ -18,3 +18,5 @@ qtConfig(wayland-shm-emulation-server-buffer): \ SUBDIRS += shm-emulation-server qtConfig(wayland-dmabuf-server-buffer): \ SUBDIRS += dmabuf-server +qtConfig(wayland-vulkan-server-buffer): \ + SUBDIRS += vulkan-server diff --git a/src/plugins/hardwareintegration/client/vulkan-server/main.cpp b/src/plugins/hardwareintegration/client/vulkan-server/main.cpp new file mode 100644 index 000000000..b8f64bf22 --- /dev/null +++ b/src/plugins/hardwareintegration/client/vulkan-server/main.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWaylandClient/private/qwaylandserverbufferintegrationplugin_p.h> +#include "vulkanserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class VulkanServerBufferPlugin : public QWaylandServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandServerBufferIntegrationFactoryInterface_iid FILE "vulkan-server.json") +public: + QWaylandServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandServerBufferIntegration *VulkanServerBufferPlugin::create(const QString& key, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(key); + return new VulkanServerBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/hardwareintegration/client/vulkan-server/vulkan-server.json b/src/plugins/hardwareintegration/client/vulkan-server/vulkan-server.json new file mode 100644 index 000000000..baadd1529 --- /dev/null +++ b/src/plugins/hardwareintegration/client/vulkan-server/vulkan-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "vulkan-server" ] +} diff --git a/src/plugins/hardwareintegration/client/vulkan-server/vulkan-server.pro b/src/plugins/hardwareintegration/client/vulkan-server/vulkan-server.pro new file mode 100644 index 000000000..1be60f7c1 --- /dev/null +++ b/src/plugins/hardwareintegration/client/vulkan-server/vulkan-server.pro @@ -0,0 +1,15 @@ +# We have a bunch of C code with casts, so we can't have this option +QMAKE_CXXFLAGS_WARN_ON -= -Wcast-qual + +QT += waylandclient-private + +include(../../../../hardwareintegration/client/vulkan-server/vulkan-server.pri) + +OTHER_FILES += \ + vulkan-server.json + +SOURCES += main.cpp + +PLUGIN_TYPE = wayland-graphics-integration-client +PLUGIN_CLASS_NAME = VulkanServerBufferPlugin +load(qt_plugin) diff --git a/src/plugins/hardwareintegration/compositor/compositor.pro b/src/plugins/hardwareintegration/compositor/compositor.pro index 59ea91414..32edf1f15 100644 --- a/src/plugins/hardwareintegration/compositor/compositor.pro +++ b/src/plugins/hardwareintegration/compositor/compositor.pro @@ -20,6 +20,8 @@ qtConfig(wayland-shm-emulation-server-buffer): \ SUBDIRS += shm-emulation-server qtConfig(wayland-dmabuf-server-buffer): \ SUBDIRS += dmabuf-server +qtConfig(wayland-vulkan-server-buffer): \ + SUBDIRS += vulkan-server qtConfig(wayland-egl): \ SUBDIRS += wayland-eglstream-controller diff --git a/src/plugins/hardwareintegration/compositor/vulkan-server/main.cpp b/src/plugins/hardwareintegration/compositor/vulkan-server/main.cpp new file mode 100644 index 000000000..d765dd389 --- /dev/null +++ b/src/plugins/hardwareintegration/compositor/vulkan-server/main.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWaylandCompositor/private/qwlserverbufferintegrationplugin_p.h> +#include "vulkanserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +class VulkanServerBufferIntegrationPlugin : public QtWayland::ServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QtWaylandServerBufferIntegrationFactoryInterface_iid FILE "vulkan-server.json") +public: + QtWayland::ServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QtWayland::ServerBufferIntegration *VulkanServerBufferIntegrationPlugin::create(const QString& key, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(key); + return new VulkanServerBufferIntegration(); +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/hardwareintegration/compositor/vulkan-server/vulkan-server.json b/src/plugins/hardwareintegration/compositor/vulkan-server/vulkan-server.json new file mode 100644 index 000000000..baadd1529 --- /dev/null +++ b/src/plugins/hardwareintegration/compositor/vulkan-server/vulkan-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "vulkan-server" ] +} diff --git a/src/plugins/hardwareintegration/compositor/vulkan-server/vulkan-server.pro b/src/plugins/hardwareintegration/compositor/vulkan-server/vulkan-server.pro new file mode 100644 index 000000000..053654218 --- /dev/null +++ b/src/plugins/hardwareintegration/compositor/vulkan-server/vulkan-server.pro @@ -0,0 +1,12 @@ +QT = waylandcompositor waylandcompositor-private core-private gui-private + +OTHER_FILES += vulkan-server.json + +SOURCES += \ + main.cpp + +include($PWD/../../../../../hardwareintegration/compositor/vulkan-server/vulkan-server.pri) + +PLUGIN_TYPE = wayland-graphics-integration-server +PLUGIN_CLASS_NAME = VulkanServerBufferIntegrationPlugin +load(qt_plugin) diff --git a/src/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp b/src/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp index 9a829f6e9..26f598895 100644 --- a/src/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp +++ b/src/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp @@ -51,7 +51,7 @@ QWaylandFullScreenShellV1Surface::QWaylandFullScreenShellV1Surface(QtWayland::zw , m_window(window) { auto screen = static_cast<QWaylandScreen *>(m_window->screen()); - m_shell->present_surface(m_window->object(), + m_shell->present_surface(m_window->wlSurface(), QtWayland::zwp_fullscreen_shell_v1::present_method_default, screen->output()); } diff --git a/src/plugins/shellintegration/ivi-shell/qwaylandivishellintegration.cpp b/src/plugins/shellintegration/ivi-shell/qwaylandivishellintegration.cpp index efb61dc9f..9309faccd 100644 --- a/src/plugins/shellintegration/ivi-shell/qwaylandivishellintegration.cpp +++ b/src/plugins/shellintegration/ivi-shell/qwaylandivishellintegration.cpp @@ -132,7 +132,7 @@ QWaylandShellSurface *QWaylandIviShellIntegration::createShellSurface(QWaylandWi if (surfaceId == 0) return nullptr; - struct ivi_surface *surface = m_iviApplication->surface_create(surfaceId, window->object()); + struct ivi_surface *surface = m_iviApplication->surface_create(surfaceId, window->wlSurface()); if (!m_iviController) return new QWaylandIviSurface(surface, window); diff --git a/src/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp b/src/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp index 1edb24b3c..f396e8402 100644 --- a/src/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp +++ b/src/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp @@ -70,7 +70,7 @@ bool QWaylandWlShellIntegration::initialize(QWaylandDisplay *display) QWaylandShellSurface *QWaylandWlShellIntegration::createShellSurface(QWaylandWindow *window) { - return new QWaylandWlShellSurface(m_wlShell->get_shell_surface(window->object()), window); + return new QWaylandWlShellSurface(m_wlShell->get_shell_surface(window->wlSurface()), window); } void *QWaylandWlShellIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window) diff --git a/src/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp b/src/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp index 4506c312a..48e14c753 100644 --- a/src/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp +++ b/src/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp @@ -62,9 +62,9 @@ QWaylandWlShellSurface::QWaylandWlShellSurface(struct ::wl_shell_surface *shell_ Qt::WindowType type = window->window()->type(); auto *transientParent = window->transientParent(); - if (type == Qt::Popup && transientParent && transientParent->object()) + if (type == Qt::Popup && transientParent && transientParent->wlSurface()) setPopup(transientParent, m_window->display()->lastInputDevice(), m_window->display()->lastInputSerial()); - else if (transientParent && transientParent->object()) + else if (transientParent && transientParent->wlSurface()) updateTransientParent(transientParent->window()); else setTopLevel(); @@ -234,11 +234,9 @@ void QWaylandWlShellSurface::updateTransientParent(QWindow *parent) || testShowWithoutActivating(m_window->window())) flags |= WL_SHELL_SURFACE_TRANSIENT_INACTIVE; - Q_ASSERT(parent_wayland_window->object()); - set_transient(parent_wayland_window->object(), - transientPos.x(), - transientPos.y(), - flags); + auto *parentSurface = parent_wayland_window->wlSurface(); + Q_ASSERT(parentSurface); + set_transient(parentSurface, transientPos.x(), transientPos.y(), flags); } void QWaylandWlShellSurface::setPopup(QWaylandWindow *parent, QWaylandInputDevice *device, uint serial) @@ -261,9 +259,10 @@ void QWaylandWlShellSurface::setPopup(QWaylandWindow *parent, QWaylandInputDevic transientPos.setY(transientPos.y() + parent_wayland_window->decoration()->margins().top()); } - Q_ASSERT(parent_wayland_window->object()); - set_popup(device->wl_seat(), serial, parent_wayland_window->object(), - transientPos.x(), transientPos.y(), 0); + auto *parentSurface = parent_wayland_window->wlSurface(); + Q_ASSERT(parentSurface); + uint flags = 0; + set_popup(device->wl_seat(), serial, parentSurface, transientPos.x(), transientPos.y(), flags); } void QWaylandWlShellSurface::shell_surface_ping(uint32_t serial) diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp index 3eda43d7c..7e242c4a5 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp @@ -74,7 +74,7 @@ QWaylandXdgPopupV5 *QWaylandXdgShellV5::createXdgPopup(QWaylandWindow *window, Q if (!parentWindow) return nullptr; - ::wl_surface *parentSurface = parentWindow->object(); + ::wl_surface *parentSurface = parentWindow->wlSurface(); if (m_popupSerial == 0) m_popupSerial = inputDevice->serial(); @@ -84,7 +84,7 @@ QWaylandXdgPopupV5 *QWaylandXdgShellV5::createXdgPopup(QWaylandWindow *window, Q int x = position.x() + parentWindow->frameMargins().left(); int y = position.y() + parentWindow->frameMargins().top(); - auto popup = new QWaylandXdgPopupV5(get_xdg_popup(window->object(), parentSurface, seat, m_popupSerial, x, y), window); + auto popup = new QWaylandXdgPopupV5(get_xdg_popup(window->wlSurface(), parentSurface, seat, m_popupSerial, x, y), window); m_popups.append(window); QObject::connect(popup, &QWaylandXdgPopupV5::destroyed, [this, window](){ m_popups.removeOne(window); diff --git a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgsurfacev5.cpp b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgsurfacev5.cpp index e9f64e2e6..e8bff9193 100644 --- a/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgsurfacev5.cpp +++ b/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgsurfacev5.cpp @@ -54,7 +54,7 @@ namespace QtWaylandClient { QWaylandXdgSurfaceV5::QWaylandXdgSurfaceV5(QWaylandXdgShellV5 *shell, QWaylandWindow *window) : QWaylandShellSurface(window) - , QtWayland::xdg_surface_v5(shell->get_xdg_surface(window->object())) + , QtWayland::xdg_surface_v5(shell->get_xdg_surface(window->wlSurface())) , m_window(window) , m_shell(shell) { diff --git a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp index 980e4a601..e81aa9038 100644 --- a/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp +++ b/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp @@ -425,7 +425,7 @@ QWaylandXdgShellV6::~QWaylandXdgShellV6() QWaylandXdgSurfaceV6 *QWaylandXdgShellV6::getXdgSurface(QWaylandWindow *window) { - return new QWaylandXdgSurfaceV6(this, get_xdg_surface(window->object()), window); + return new QWaylandXdgSurfaceV6(this, get_xdg_surface(window->wlSurface()), window); } void QWaylandXdgShellV6::zxdg_shell_v6_ping(uint32_t serial) diff --git a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index f55298134..ca8da80b7 100644 --- a/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -463,7 +463,7 @@ QWaylandXdgShell::~QWaylandXdgShell() QWaylandXdgSurface *QWaylandXdgShell::getXdgSurface(QWaylandWindow *window) { - return new QWaylandXdgSurface(this, get_xdg_surface(window->object()), window); + return new QWaylandXdgSurface(this, get_xdg_surface(window->wlSurface()), window); } void QWaylandXdgShell::xdg_wm_base_ping(uint32_t serial) diff --git a/src/qtwaylandscanner/qtwaylandscanner.cpp b/src/qtwaylandscanner/qtwaylandscanner.cpp index 56045e880..7f3dc5ad6 100644 --- a/src/qtwaylandscanner/qtwaylandscanner.cpp +++ b/src/qtwaylandscanner/qtwaylandscanner.cpp @@ -986,6 +986,7 @@ bool Scanner::process() printf("\n"); printf(" struct ::%s *object() { return m_%s; }\n", interfaceName, interfaceName); printf(" const struct ::%s *object() const { return m_%s; }\n", interfaceName, interfaceName); + printf(" static %s *fromObject(struct ::%s *object);\n", interfaceName, interfaceName); printf("\n"); printf(" bool isInitialized() const;\n"); printf("\n"); @@ -1130,6 +1131,16 @@ bool Scanner::process() printf(" }\n"); printf("\n"); + printf(" %s *%s::fromObject(struct ::%s *object)\n", interfaceName, interfaceName, interfaceName); + printf(" {\n"); + if (hasEvents) { + printf(" if (wl_proxy_get_listener((struct ::wl_proxy *)object) != (void *)&m_%s_listener)\n", interfaceName); + printf(" return nullptr;\n"); + } + printf(" return static_cast<%s *>(%s_get_user_data(object));\n", interfaceName, interfaceName); + printf(" }\n"); + printf("\n"); + printf(" bool %s::isInitialized() const\n", interfaceName); printf(" {\n"); printf(" return m_%s != nullptr;\n", interfaceName); diff --git a/src/src.pro b/src/src.pro index 031ff885c..d4244de33 100644 --- a/src/src.pro +++ b/src/src.pro @@ -28,7 +28,7 @@ qtConfig(wayland-client) { SUBDIRS += sub_compositor sub_imports.subdir = imports - sub_imports.depends += sub-compositor + sub_imports.depends += sub-compositor sub-client sub_imports.target = sub-imports SUBDIRS += sub_imports } diff --git a/sync.profile b/sync.profile index a06caa377..b754b4d16 100644 --- a/sync.profile +++ b/sync.profile @@ -27,6 +27,7 @@ "^qwayland-text-input-unstable-v2.h", "^qwayland-touch-extension.h", "^qwayland-wayland.h", + "^qwayland-wp-primary-selection-unstable-v1.h", "^qwayland-xdg-output-unstable-v1.h", "^wayland-hardware-integration-client-protocol.h", "^wayland-qt-windowmanager-client-protocol.h", @@ -36,6 +37,7 @@ "^wayland-text-input-unstable-v2-client-protocol.h", "^wayland-touch-extension-client-protocol.h", "^wayland-wayland-client-protocol.h", + "^wayland-wp-primary-selection-unstable-v1-client-protocol.h", "^wayland-xdg-output-unstable-v1-client-protocol.h", ], "$basedir/src/plugins/shellintegration/xdg-shell" => [ @@ -58,6 +60,7 @@ "^qwayland-server-ivi-application.h", "^qwayland-server-qt-windowmanager.h", "^qwayland-server-qt-key-unstable-v1.h", + "^qwayland-server-qt-texture-sharing-unstable-v1.h", "^qwayland-server-scaler.h", "^qwayland-server-server-buffer-extension.h", "^qwayland-server-text-input-unstable-v2.h", @@ -71,6 +74,7 @@ "^wayland-ivi-application-server-protocol.h", "^wayland-qt-windowmanager-server-protocol.h", "^wayland-qt-key-unstable-v1-server-protocol.h", + "^wayland-qt-texture-sharing-unstable-v1-server-protocol.h", "^wayland-scaler-server-protocol.h", "^wayland-server-buffer-extension-server-protocol.h", "^wayland-text-input-unstable-v2-server-protocol.h", diff --git a/tests/auto/client/client.pro b/tests/auto/client/client.pro index 051cb4e3d..61bf67853 100644 --- a/tests/auto/client/client.pro +++ b/tests/auto/client/client.pro @@ -6,7 +6,9 @@ SUBDIRS += \ fullscreenshellv1 \ iviapplication \ output \ + primaryselectionv1 \ seatv4 \ + seatv5 \ surface \ wl_connect \ xdgoutput \ diff --git a/tests/auto/client/client/tst_client.cpp b/tests/auto/client/client/tst_client.cpp index 08120c8c2..e9ae5e4b3 100644 --- a/tests/auto/client/client/tst_client.cpp +++ b/tests/auto/client/client/tst_client.cpp @@ -36,7 +36,9 @@ #include <QPixmap> #include <QDrag> #include <QWindow> +#if QT_CONFIG(opengl) #include <QOpenGLWindow> +#endif #include <QtTest/QtTest> #include <QtWaylandClient/private/qwaylandintegration_p.h> @@ -107,6 +109,7 @@ public: QPoint mousePressPos; }; +#if QT_CONFIG(opengl) class TestGlWindow : public QOpenGLWindow { Q_OBJECT @@ -136,6 +139,7 @@ void TestGlWindow::paintGL() glClear(GL_COLOR_BUFFER_BIT); ++paintGLCalled; } +#endif // QT_CONFIG(opengl) class tst_WaylandClient : public QObject { @@ -176,7 +180,9 @@ private slots: void dontCrashOnMultipleCommits(); void hiddenTransientParent(); void hiddenPopupParent(); +#if QT_CONFIG(opengl) void glWindow(); +#endif // QT_CONFIG(opengl) void longWindowTitle(); private: @@ -458,6 +464,7 @@ void tst_WaylandClient::hiddenPopupParent() QTRY_VERIFY(compositor->surface()); } +#if QT_CONFIG(opengl) void tst_WaylandClient::glWindow() { QSKIP("Skipping GL tests, as not supported by all CI systems: See https://bugreports.qt.io/browse/QTBUG-65802"); @@ -483,6 +490,7 @@ void tst_WaylandClient::glWindow() testWindow->setVisible(false); QTRY_VERIFY(!compositor->surface()); } +#endif // QT_CONFIG(opengl) void tst_WaylandClient::longWindowTitle() { diff --git a/tests/auto/client/primaryselectionv1/primaryselectionv1.pro b/tests/auto/client/primaryselectionv1/primaryselectionv1.pro new file mode 100644 index 000000000..9d00562df --- /dev/null +++ b/tests/auto/client/primaryselectionv1/primaryselectionv1.pro @@ -0,0 +1,7 @@ +include (../shared/shared.pri) + +WAYLANDSERVERSOURCES += \ + $$PWD/../../../../src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml + +TARGET = tst_primaryselectionv1 +SOURCES += tst_primaryselectionv1.cpp diff --git a/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp new file mode 100644 index 000000000..281e4c5d1 --- /dev/null +++ b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp @@ -0,0 +1,466 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "mockcompositor.h" + +#include <qwayland-server-wp-primary-selection-unstable-v1.h> + +#include <QtGui/QRasterWindow> +#include <QtGui/QOpenGLWindow> +#include <QtGui/QClipboard> +#include <QtCore/private/qcore_unix_p.h> + +#include <fcntl.h> + +using namespace MockCompositor; + +constexpr int primarySelectionVersion = 1; // protocol VERSION, not the name suffix (_v1) + +class PrimarySelectionDeviceV1; +class PrimarySelectionDeviceManagerV1; + +class PrimarySelectionOfferV1 : public QObject, public QtWaylandServer::zwp_primary_selection_offer_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionOfferV1(PrimarySelectionDeviceV1 *device, wl_client *client, int version) + : zwp_primary_selection_offer_v1(client, 0, version) + , m_device(device) + {} + void send_offer() = delete; + void sendOffer(const QString &offer) + { + zwp_primary_selection_offer_v1::send_offer(offer); + m_mimeTypes << offer; + } + + PrimarySelectionDeviceV1 *m_device = nullptr; + QStringList m_mimeTypes; + +signals: + void receive(QString mimeType, int fd); + +protected: + void zwp_primary_selection_offer_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } + + void zwp_primary_selection_offer_v1_receive(Resource *resource, const QString &mime_type, int32_t fd) override + { + Q_UNUSED(resource); + QTRY_VERIFY(m_mimeTypes.contains(mime_type)); + emit receive(mime_type, fd); + } + + void zwp_primary_selection_offer_v1_destroy(Resource *resource) override; +}; + +class PrimarySelectionSourceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_source_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionSourceV1(wl_client *client, int id, int version) + : zwp_primary_selection_source_v1(client, id, version) + { + } + QStringList m_offers; +protected: + void zwp_primary_selection_source_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } + void zwp_primary_selection_source_v1_offer(Resource *resource, const QString &mime_type) override + { + Q_UNUSED(resource); + m_offers << mime_type; + } + void zwp_primary_selection_source_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } +}; + +class PrimarySelectionDeviceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_device_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionDeviceV1(PrimarySelectionDeviceManagerV1 *manager, Seat *seat) + : m_manager(manager) + , m_seat(seat) + {} + + void send_data_offer(::wl_resource *resource) = delete; + + PrimarySelectionOfferV1 *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {}); + + PrimarySelectionOfferV1 *sendDataOffer(const QStringList &mimeTypes = {}) // creates a new offer for the focused surface and sends it + { + Q_ASSERT(m_seat->m_capabilities & Seat::capability_keyboard); + Q_ASSERT(m_seat->m_keyboard->m_enteredSurface); + auto *client = m_seat->m_keyboard->m_enteredSurface->resource()->client(); + return sendDataOffer(client, mimeTypes); + } + + void send_selection(::wl_resource *resource) = delete; + void sendSelection(PrimarySelectionOfferV1 *offer) + { + auto *client = offer->resource()->client(); + for (auto *resource : resourceMap().values(client)) + zwp_primary_selection_device_v1::send_selection(resource->handle, offer->resource()->handle); + m_sentSelectionOffers << offer; + } + + PrimarySelectionDeviceManagerV1 *m_manager = nullptr; + Seat *m_seat = nullptr; + QVector<PrimarySelectionOfferV1 *> m_sentSelectionOffers; + PrimarySelectionSourceV1 *m_selectionSource = nullptr; + uint m_serial = 0; + +protected: + void zwp_primary_selection_device_v1_set_selection(Resource *resource, ::wl_resource *source, uint32_t serial) override + { + Q_UNUSED(resource); + m_selectionSource = fromResource<PrimarySelectionSourceV1>(source); + m_serial = serial; + } + void zwp_primary_selection_device_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + void zwp_primary_selection_device_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } +}; + +class PrimarySelectionDeviceManagerV1 : public Global, public QtWaylandServer::zwp_primary_selection_device_manager_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionDeviceManagerV1(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::zwp_primary_selection_device_manager_v1(compositor->m_display, version) + , m_version(version) + {} + bool isClean() override + { + for (auto *device : qAsConst(m_devices)) { + // The client should not leak selection offers, i.e. if this fails, there is a missing + // zwp_primary_selection_offer_v1.destroy request + if (!device->m_sentSelectionOffers.empty()) + return false; + } + return true; + } + + PrimarySelectionDeviceV1 *deviceFor(Seat *seat) + { + Q_ASSERT(seat); + if (auto *device = m_devices.value(seat, nullptr)) + return device; + + auto *device = new PrimarySelectionDeviceV1(this, seat); + m_devices[seat] = device; + return device; + } + + int m_version = 1; // TODO: Remove on libwayland upgrade + QMap<Seat *, PrimarySelectionDeviceV1 *> m_devices; + QVector<PrimarySelectionSourceV1 *> m_sources; +protected: + void zwp_primary_selection_device_manager_v1_destroy(Resource *resource) override + { + // The protocol doesn't say whether managed objects should be destroyed as well, + // so leave them alone, they'll be cleaned up in the destructor anyway + wl_resource_destroy(resource->handle); + } + + void zwp_primary_selection_device_manager_v1_create_source(Resource *resource, uint32_t id) override + { + int version = m_version; + m_sources << new PrimarySelectionSourceV1(resource->client(), id, version); + } + void zwp_primary_selection_device_manager_v1_get_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override + { + auto *seat = fromResource<Seat>(seatResource); + QVERIFY(seat); + auto *device = deviceFor(seat); + device->add(resource->client(), id, resource->version()); + } +}; + +PrimarySelectionOfferV1 *PrimarySelectionDeviceV1::sendDataOffer(wl_client *client, const QStringList &mimeTypes) +{ + Q_ASSERT(client); + auto *offer = new PrimarySelectionOfferV1(this, client, m_manager->m_version); + for (auto *resource : resourceMap().values(client)) + zwp_primary_selection_device_v1::send_data_offer(resource->handle, offer->resource()->handle); + for (const auto &mimeType : mimeTypes) + offer->sendOffer(mimeType); + return offer; +} + +void PrimarySelectionOfferV1::zwp_primary_selection_offer_v1_destroy(QtWaylandServer::zwp_primary_selection_offer_v1::Resource *resource) +{ + bool removed = m_device->m_sentSelectionOffers.removeOne(this); + QVERIFY(removed); + wl_resource_destroy(resource->handle); +} + +class PrimarySelectionCompositor : public DefaultCompositor { +public: + explicit PrimarySelectionCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add<PrimarySelectionDeviceManagerV1>(primarySelectionVersion); + }); + } + PrimarySelectionDeviceV1 *primarySelectionDevice(int i = 0) { + return get<PrimarySelectionDeviceManagerV1>()->deviceFor(get<Seat>(i)); + } +}; + +class tst_primaryselectionv1 : public QObject, private PrimarySelectionCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void initTestCase(); + void bindsToManager(); + void createsPrimaryDevice(); + void createsPrimaryDeviceForNewSeats(); + void pasteAscii(); + void pasteUtf8(); + void destroysPreviousSelection(); + void copy(); +}; + +void tst_primaryselectionv1::initTestCase() +{ + QCOMPOSITOR_TRY_VERIFY(pointer()); + QCOMPOSITOR_TRY_VERIFY(!pointer()->resourceMap().empty()); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 4); + + QCOMPOSITOR_TRY_VERIFY(keyboard()); +} + +void tst_primaryselectionv1::bindsToManager() +{ + QCOMPOSITOR_TRY_COMPARE(get<PrimarySelectionDeviceManagerV1>()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(get<PrimarySelectionDeviceManagerV1>()->resourceMap().first()->version(), primarySelectionVersion); +} + +void tst_primaryselectionv1::createsPrimaryDevice() +{ + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->resourceMap().contains(client())); + QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->resourceMap().value(client())->version(), primarySelectionVersion); + QTRY_VERIFY(QGuiApplication::clipboard()->supportsSelection()); +} + +void tst_primaryselectionv1::createsPrimaryDeviceForNewSeats() +{ + exec([=] { add<Seat>(); }); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice(1)); +} + +void tst_primaryselectionv1::pasteAscii() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); + m_formats = mimeData->formats(); + m_text = QGuiApplication::clipboard()->text(QClipboard::Selection); + } + QStringList m_formats; + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *device = primarySelectionDevice(); + auto *offer = device->sendDataOffer({"text/plain"}); + connect(offer, &PrimarySelectionOfferV1::receive, [](QString mimeType, int fd) { + QFile file; + file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); + QCOMPARE(mimeType, "text/plain"); + file.write(QByteArray("normal ascii")); + file.close(); + }); + device->sendSelection(offer); + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendButton(client(), BTN_MIDDLE, 0); + }); + QTRY_COMPARE(window.m_formats, QStringList{"text/plain"}); + QTRY_COMPARE(window.m_text, "normal ascii"); +} + +void tst_primaryselectionv1::pasteUtf8() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); + m_formats = mimeData->formats(); + m_text = QGuiApplication::clipboard()->text(QClipboard::Selection); + } + QStringList m_formats; + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *device = primarySelectionDevice(); + auto *offer = device->sendDataOffer({"text/plain", "text/plain;charset=utf-8"}); + connect(offer, &PrimarySelectionOfferV1::receive, [](QString mimeType, int fd) { + QFile file; + file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); + QCOMPARE(mimeType, "text/plain;charset=utf-8"); + file.write(QByteArray("face with tears of joy: 😂")); + file.close(); + }); + device->sendSelection(offer); + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendButton(client(), BTN_MIDDLE, 0); + }); + QTRY_COMPARE(window.m_formats, QStringList({"text/plain", "text/plain;charset=utf-8"})); + QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); +} + +void tst_primaryselectionv1::destroysPreviousSelection() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + // When the client receives a selection event, it is required to destroy the previous offer + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); + primarySelectionDevice()->sendSelection(offer); + }); + + exec([&] { + auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); + primarySelectionDevice()->sendSelection(offer); + QCOMPARE(primarySelectionDevice()->m_sentSelectionOffers.size(), 2); + }); + + // Verify the first offer gets destroyed + QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->m_sentSelectionOffers.size(), 1); +} + +void tst_primaryselectionv1::copy() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + QGuiApplication::clipboard()->setText("face with tears of joy: 😂", QClipboard::Selection); + } + QStringList m_formats; + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + QVector<uint> mouseSerials; + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + pointer()->sendEnter(surface, {32, 32}); + mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 1); + mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 0); + }); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->m_selectionSource); + QCOMPOSITOR_TRY_VERIFY(mouseSerials.contains(primarySelectionDevice()->m_serial)); + QByteArray pastedBuf; + exec([&](){ + auto *source = primarySelectionDevice()->m_selectionSource; + QCOMPARE(source->m_offers, QStringList({"text/plain", "text/plain;charset=utf-8"})); + int fd[2]; + if (pipe(fd) == -1) + QSKIP("Failed to create pipe"); + fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL, 0) | O_NONBLOCK); + source->send_send("text/plain;charset=utf-8", fd[1]); + auto *notifier = new QSocketNotifier(fd[0], QSocketNotifier::Read, this); + connect(notifier, &QSocketNotifier::activated, this, [&](int fd) { + exec([&]{ + static char buf[1024]; + int n = QT_READ(fd, buf, sizeof buf); + if (n <= 0) { + delete notifier; + close(fd); + } else { + pastedBuf.append(buf, n); + } + }); + }); + }); + + QCOMPOSITOR_TRY_VERIFY(pastedBuf.size()); // this assumes we got everything in one read + auto pasted = QString::fromUtf8(pastedBuf); + QCOMPARE(pasted, "face with tears of joy: 😂"); +} + +QCOMPOSITOR_TEST_MAIN(tst_primaryselectionv1) +#include "tst_primaryselectionv1.moc" diff --git a/tests/auto/client/seatv4/tst_seatv4.cpp b/tests/auto/client/seatv4/tst_seatv4.cpp index 7dc2e727a..77304deaf 100644 --- a/tests/auto/client/seatv4/tst_seatv4.cpp +++ b/tests/auto/client/seatv4/tst_seatv4.cpp @@ -80,6 +80,7 @@ private slots: void bitmapCursor(); void hidpiBitmapCursor(); void hidpiBitmapCursorNonInt(); + void animatedCursor(); #endif }; @@ -554,6 +555,36 @@ void tst_seatv4::hidpiBitmapCursorNonInt() QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25)); } +void tst_seatv4::animatedCursor() +{ + QRasterWindow window; + window.resize(64, 64); + window.setCursor(Qt::WaitCursor); // TODO: verify that the theme has an animated wait cursor or skip test + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + + // We should get the first buffer without waiting for a frame callback + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QSignalSpy bufferSpy(exec([=] { return cursorSurface(); }), &Surface::bufferCommitted); + + exec([&] { + // Make sure no extra buffers have arrived + QVERIFY(bufferSpy.empty()); + + // The client should send a frame request in order to time animations correctly + QVERIFY(!cursorSurface()->m_waitingFrameCallbacks.empty()); + + // Tell the client it's time to animate + cursorSurface()->sendFrameCallbacks(); + }); + + // Verify that we get a new cursor buffer + QTRY_COMPARE(bufferSpy.count(), 1); +} + #endif // QT_CONFIG(cursor) QCOMPOSITOR_TEST_MAIN(tst_seatv4) diff --git a/tests/auto/client/seatv5/seatv5.pro b/tests/auto/client/seatv5/seatv5.pro new file mode 100644 index 000000000..2081845ef --- /dev/null +++ b/tests/auto/client/seatv5/seatv5.pro @@ -0,0 +1,4 @@ +include (../shared/shared.pri) + +TARGET = tst_seatv5 +SOURCES += tst_seatv5.cpp diff --git a/tests/auto/client/seatv5/tst_seatv5.cpp b/tests/auto/client/seatv5/tst_seatv5.cpp new file mode 100644 index 000000000..5b9235d9a --- /dev/null +++ b/tests/auto/client/seatv5/tst_seatv5.cpp @@ -0,0 +1,387 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/QOpenGLWindow> + +using namespace MockCompositor; + +class SeatV5Compositor : public DefaultCompositor { +public: + explicit SeatV5Compositor() + { + exec([this] { + m_config.autoConfigure = true; + + removeAll<Seat>(); + + uint capabilities = MockCompositor::Seat::capability_pointer; + int version = 5; + add<Seat>(capabilities, version); + }); + } + + Pointer *pointer() + { + auto *seat = get<Seat>(); + Q_ASSERT(seat); + return seat->m_pointer; + } +}; + +class tst_seatv5 : public QObject, private SeatV5Compositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void bindsToSeat(); + void createsPointer(); + void setsCursorOnEnter(); + void usesEnterSerial(); + void simpleAxis_data(); + void simpleAxis(); + void fingerScroll(); + void fingerScrollSlow(); + void wheelDiscreteScroll(); +}; + +void tst_seatv5::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 5); +} + +void tst_seatv5::createsPointer() +{ + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 5); +} + +void tst_seatv5::setsCursorOnEnter() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {0, 0}); + pointer()->sendFrame(surface->resource()->client()); + }); + + QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()); +} + +void tst_seatv5::usesEnterSerial() +{ + QSignalSpy setCursorSpy(exec([=] { return pointer(); }), &Pointer::setCursor); + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + uint enterSerial = exec([=] { + return pointer()->sendEnter(xdgSurface()->m_surface, {0, 0}); + }); + QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()); + + QTRY_COMPARE(setCursorSpy.count(), 1); + QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial); +} + +class WheelWindow : QRasterWindow { +public: + WheelWindow() + { + resize(64, 64); + show(); + } + void wheelEvent(QWheelEvent *event) override + { + QRasterWindow::wheelEvent(event); +// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta(); + + if (event->phase() == Qt::ScrollUpdate || event->phase() == Qt::NoScrollPhase) { + // Angle delta should always be provided (says docs, but QPA sends compatibility events + // for Qt4 with zero angleDelta, and with a delta) + QVERIFY(!event->angleDelta().isNull() || event->delta()); + } else { + // Shouldn't have deltas in the other phases + QCOMPARE(event->angleDelta(), QPoint(0, 0)); + QCOMPARE(event->pixelDelta(), QPoint(0, 0)); + } + + // The axis vector of the event is already in surface space, so there is now way to tell + // whether it is inverted or not. + QCOMPARE(event->inverted(), false); + + // We didn't press any buttons + QCOMPARE(event->buttons(), Qt::NoButton); + + if (!event->angleDelta().isNull()) { + if (event->orientation() == Qt::Horizontal) + QCOMPARE(event->delta(), event->angleDelta().x()); + else + QCOMPARE(event->delta(), event->angleDelta().y()); + } + + m_events.append(Event{event}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + explicit Event() = default; + explicit Event(const QWheelEvent *event) + : phase(event->phase()) + , pixelDelta(event->pixelDelta()) + , angleDelta(event->angleDelta()) + , orientation(event->orientation()) + , source(event->source()) + { + } + const Qt::ScrollPhase phase{}; + const QPoint pixelDelta; + const QPoint angleDelta; // eights of a degree, positive is upwards, left + const Qt::Orientation orientation{}; + const Qt::MouseEventSource source{}; + }; + QVector<Event> m_events; +}; + +void tst_seatv5::simpleAxis_data() +{ + QTest::addColumn<uint>("axis"); + QTest::addColumn<qreal>("value"); + QTest::addColumn<Qt::Orientation>("orientation"); + QTest::addColumn<QPoint>("angleDelta"); + + // Directions in regular windows/linux terms (no "natural" scrolling) + QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << Qt::Vertical << QPoint{0, -12}; + QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << Qt::Vertical << QPoint{0, 12}; + QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << Qt::Horizontal << QPoint{-12, 0}; + QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << Qt::Horizontal << QPoint{12, 0}; + QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << Qt::Vertical << QPoint{0, 120}; +} + +void tst_seatv5::simpleAxis() +{ + QFETCH(uint, axis); + QFETCH(qreal, value); + QFETCH(Qt::Orientation, orientation); + QFETCH(QPoint, angleDelta); + + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *p = pointer(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(client()); + p->sendAxis( + client(), + Pointer::axis(axis), + value // Length of vector in surface-local space. i.e. positive is downwards + ); + p->sendFrame(client()); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + // Pixel delta should only be set if we know it's a high-res input device (which we don't) + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + // There has been no information about what created the event. + // Documentation says not synthesized is appropriate in such cases + QCOMPARE(e.source, Qt::MouseEventNotSynthesized); + QCOMPARE(e.orientation, orientation); + QCOMPARE(e.angleDelta, angleDelta); + } + + // Sending axis_stop is not mandatory when axis source != finger +} + +void tst_seatv5::fingerScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source_finger); + p->sendAxis(c, Pointer::axis_vertical_scroll, 10); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollBegin); + QCOMPARE(e.angleDelta, QPoint()); + QCOMPARE(e.pixelDelta, QPoint()); + } + + QTRY_VERIFY(!window.m_events.empty()); + // For some reason we send two ScrollBegins, one for each direction, not sure if this is really + // necessary, (could be removed from QtBase, hence the conditional below. + if (window.m_events.first().phase == Qt::ScrollBegin) { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.angleDelta, QPoint()); + QCOMPARE(e.pixelDelta, QPoint()); + } + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QCOMPARE(e.orientation, Qt::Vertical); +// QCOMPARE(e.angleDelta, angleDelta); // TODO: what should this be? + QCOMPARE(e.pixelDelta, QPoint(0, 10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + + QTRY_VERIFY(window.m_events.empty()); + + // Scroll horizontally as well + exec([=] { + pointer()->sendAxisSource(client(), Pointer::axis_source_finger); + pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10); + pointer()->sendFrame(client()); + }); + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QCOMPARE(e.orientation, Qt::Horizontal); + QCOMPARE(e.pixelDelta, QPoint(10, 0)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + + // Scroll diagonally + exec([=] { + pointer()->sendAxisSource(client(), Pointer::axis_source_finger); + pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10); + pointer()->sendAxis(client(), Pointer::axis_vertical_scroll, 10); + pointer()->sendFrame(client()); + }); + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QCOMPARE(e.pixelDelta, QPoint(10, 10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + + // For diagonal events, Qt sends an additional compatibility ScrollUpdate event + if (window.m_events.first().phase == Qt::ScrollUpdate) { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.angleDelta, QPoint()); + QCOMPARE(e.pixelDelta, QPoint()); + } + + QVERIFY(window.m_events.empty()); + + // Sending axis_stop is mandatory when axis source == finger + exec([=] { + pointer()->sendAxisStop(client(), Pointer::axis_vertical_scroll); + pointer()->sendFrame(client()); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollEnd); + } +} + + +void tst_seatv5::fingerScrollSlow() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + // Send 10 really small updates + for (int i = 0; i < 10; ++i) { + p->sendAxisSource(c, Pointer::axis_source_finger); + p->sendAxis(c, Pointer::axis_vertical_scroll, 0.1); + p->sendFrame(c); + } + p->sendAxisStop(c, Pointer::axis_vertical_scroll); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + QPoint accumulated; + while (window.m_events.first().phase != Qt::ScrollEnd) { + auto e = window.m_events.takeFirst(); + accumulated += e.pixelDelta; + QTRY_VERIFY(!window.m_events.empty()); + } + QCOMPARE(accumulated.y(), 1); +} +void tst_seatv5::wheelDiscreteScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source_wheel); + p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards + p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QCOMPARE(e.orientation, Qt::Vertical); + // According to the docs the angle delta is in eights of a degree and most mice have + // 1 click = 15 degrees. The angle delta should therefore be: + // 15 degrees / (1/8 eights per degrees) = 120 eights of degrees. + QCOMPARE(e.angleDelta, QPoint(0, -120)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } +} + +QCOMPOSITOR_TEST_MAIN(tst_seatv5) +#include "tst_seatv5.moc" diff --git a/tests/auto/client/shared/corecompositor.h b/tests/auto/client/shared/corecompositor.h index 875b7d050..254465ee6 100644 --- a/tests/auto/client/shared/corecompositor.h +++ b/tests/auto/client/shared/corecompositor.h @@ -125,6 +125,23 @@ public: } /*! + * \brief Returns the nth global with the given type, if any + */ + template<typename global_type> + global_type *get(int index) + { + warnIfNotLockedByThread(Q_FUNC_INFO); + for (auto *global : qAsConst(m_globals)) { + if (auto *casted = qobject_cast<global_type *>(global)) { + if (index--) + continue; + return casted; + } + } + return nullptr; + } + + /*! * \brief Returns all globals with the given type, if any */ template<typename global_type> diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp index 729d481f8..f9335e783 100644 --- a/tests/auto/client/shared/coreprotocol.cpp +++ b/tests/auto/client/shared/coreprotocol.cpp @@ -314,6 +314,39 @@ void Pointer::sendAxis(wl_client *client, axis axis, qreal value) send_axis(r->handle, time, axis, val); } +void Pointer::sendAxisDiscrete(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int discrete) +{ + // TODO: assert v5 or newer + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_discrete(r->handle, axis, discrete); +} + +void Pointer::sendAxisSource(wl_client *client, QtWaylandServer::wl_pointer::axis_source source) +{ + // TODO: assert v5 or newer + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_source(r->handle, source); +} + +void Pointer::sendAxisStop(wl_client *client, QtWaylandServer::wl_pointer::axis axis) +{ + // TODO: assert v5 or newer + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_stop(r->handle, time, axis); +} + +void Pointer::sendFrame(wl_client *client) +{ + //TODO: assert version 5 or newer? + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_frame(r->handle); +} + void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) { Q_UNUSED(resource); @@ -345,6 +378,7 @@ uint Keyboard::sendEnter(Surface *surface) const auto pointerResources = resourceMap().values(client); for (auto *r : pointerResources) send_enter(r->handle, serial, surface->resource()->handle, QByteArray()); + m_enteredSurface = surface; return serial; } @@ -355,6 +389,7 @@ uint Keyboard::sendLeave(Surface *surface) const auto pointerResources = resourceMap().values(client); for (auto *r : pointerResources) send_leave(r->handle, serial, surface->resource()->handle); + m_enteredSurface = nullptr; return serial; } diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h index 5cef476c8..264c5f694 100644 --- a/tests/auto/client/shared/coreprotocol.h +++ b/tests/auto/client/shared/coreprotocol.h @@ -236,7 +236,7 @@ class Seat : public Global, public QtWaylandServer::wl_seat { Q_OBJECT public: - explicit Seat(CoreCompositor *compositor, uint capabilities, int version = 4); + explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard, int version = 4); ~Seat() override; void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead void send_capabilities(uint capabilities) = delete; // Use wrapper instead @@ -279,6 +279,10 @@ public: void sendMotion(wl_client *client, const QPointF &position); uint sendButton(wl_client *client, uint button, uint state); void sendAxis(wl_client *client, axis axis, qreal value); + void sendAxisDiscrete(wl_client *client, axis axis, int discrete); + void sendAxisSource(wl_client *client, axis_source source); + void sendAxisStop(wl_client *client, axis axis); + void sendFrame(wl_client *client); Seat *m_seat = nullptr; QVector<uint> m_enterSerials; @@ -313,6 +317,7 @@ public: uint sendLeave(Surface *surface); uint sendKey(wl_client *client, uint key, uint state); Seat *m_seat = nullptr; + Surface *m_enteredSurface = nullptr; }; class Shm : public Global, public QtWaylandServer::wl_shm diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h index 75ef1eaea..aa85a4aea 100644 --- a/tests/auto/client/shared/mockcompositor.h +++ b/tests/auto/client/shared/mockcompositor.h @@ -36,10 +36,16 @@ #include <QtGui/QGuiApplication> -#ifndef BTN_LEFT // As defined in linux/input-event-codes.h +#ifndef BTN_LEFT #define BTN_LEFT 0x110 #endif +#ifndef BTN_RIGHT +#define BTN_RIGHT 0x111 +#endif +#ifndef BTN_MIDDLE +#define BTN_MIDDLE 0x112 +#endif namespace MockCompositor { diff --git a/tests/auto/client/surface/tst_surface.cpp b/tests/auto/client/surface/tst_surface.cpp index dddff0866..9659235a0 100644 --- a/tests/auto/client/surface/tst_surface.cpp +++ b/tests/auto/client/surface/tst_surface.cpp @@ -28,7 +28,9 @@ #include "mockcompositor.h" #include <QtGui/QRasterWindow> +#if QT_CONFIG(opengl) #include <QtGui/QOpenGLWindow> +#endif using namespace MockCompositor; @@ -39,7 +41,9 @@ private slots: void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } void createDestroySurface(); void waitForFrameCallbackRaster(); +#if QT_CONFIG(opengl) void waitForFrameCallbackGl(); +#endif void negotiateShmFormat(); }; @@ -89,6 +93,7 @@ void tst_surface::waitForFrameCallbackRaster() } } +#if QT_CONFIG(opengl) void tst_surface::waitForFrameCallbackGl() { QSKIP("TODO: This currently fails, needs a fix"); @@ -129,6 +134,7 @@ void tst_surface::waitForFrameCallbackGl() bufferSpy.removeFirst(); } } +#endif // QT_CONFIG(opengl) void tst_surface::negotiateShmFormat() { diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp index 9b18abdc3..038ff6624 100644 --- a/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -29,6 +29,8 @@ #include "mockcompositor.h" #include <QtGui/QRasterWindow> #include <QtGui/QOpenGLWindow> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtWaylandClient/private/wayland-wayland-client-protocol.h> using namespace MockCompositor; @@ -47,6 +49,7 @@ private slots: void pongs(); void minMaxSize(); void windowGeometry(); + void foreignSurface(); }; void tst_xdgshell::showMinimized() @@ -479,5 +482,32 @@ void tst_xdgshell::windowGeometry() QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committed.windowGeometry, QRect(QPoint(0, 0), window.frameGeometry().size())); } +void tst_xdgshell::foreignSurface() +{ + auto *ni = QGuiApplication::platformNativeInterface(); + auto *compositor = static_cast<::wl_compositor *>(ni->nativeResourceForIntegration("compositor")); + ::wl_surface *foreignSurface = wl_compositor_create_surface(compositor); + + // There *could* be cursor surfaces lying around, we don't want to confuse those with + // the foreign surface we will be creating. + const int newSurfaceIndex = exec([&]{ + return get<WlCompositor>()->m_surfaces.size(); + }); + + QCOMPOSITOR_TRY_VERIFY(surface(newSurfaceIndex)); + exec([&] { + pointer()->sendEnter(surface(newSurfaceIndex), {32, 32}); + pointer()->sendLeave(surface(newSurfaceIndex)); + }); + + // Just do something to make sure we don't destroy the surface before + // the pointer events above are handled. + QSignalSpy spy(exec([=] { return surface(newSurfaceIndex); }), &Surface::commit); + wl_surface_commit(foreignSurface); + QTRY_COMPARE(spy.count(), 1); + + wl_surface_destroy(foreignSurface); +} + QCOMPOSITOR_TEST_MAIN(tst_xdgshell) #include "tst_xdgshell.moc" diff --git a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp index a397f60eb..027e1dfa8 100644 --- a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp +++ b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp @@ -413,11 +413,11 @@ void tst_WaylandClientXdgShellV6::dontSpamExposeEvents() QSharedPointer<MockSurface> surface; QTRY_VERIFY(surface = m_compositor->surface()); - QTRY_VERIFY(window.exposeEventCount == 0); + QTRY_COMPARE(window.exposeEventCount, 0); m_compositor->sendShellSurfaceConfigure(surface); QTRY_VERIFY(window.isExposed()); - QTRY_VERIFY(window.exposeEventCount == 1); + QTRY_COMPARE(window.exposeEventCount, 1); } int main(int argc, char **argv) diff --git a/tests/manual/texture-sharing/cpp-client/cpp-client.pro b/tests/manual/texture-sharing/cpp-client/cpp-client.pro new file mode 100644 index 000000000..d251791db --- /dev/null +++ b/tests/manual/texture-sharing/cpp-client/cpp-client.pro @@ -0,0 +1,15 @@ +QT += waylandclient-private gui-private +CONFIG += wayland-scanner + +WAYLANDCLIENTSOURCES += $$PWD/../../../../src/extensions/qt-texture-sharing-unstable-v1.xml + +SOURCES += main.cpp \ + $$PWD/../../../../src/imports/texture-sharing/texturesharingextension.cpp + +HEADERS += \ + $$PWD/../../../../src/imports/texture-sharing/texturesharingextension.h + +INCLUDEPATH += $$PWD/../../../../src/imports/texture-sharing/ + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/texture-sharing/cpp-client +INSTALLS += target diff --git a/tests/manual/texture-sharing/cpp-client/main.cpp b/tests/manual/texture-sharing/cpp-client/main.cpp new file mode 100644 index 000000000..e3f6d7025 --- /dev/null +++ b/tests/manual/texture-sharing/cpp-client/main.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** + ** + ** Copyright (C) 2019 The Qt Company Ltd. + ** Contact: https://www.qt.io/licensing/ + ** + ** This file is part of the examples of the Qt Wayland module + ** + ** $QT_BEGIN_LICENSE:BSD$ + ** 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. + ** + ** BSD License Usage + ** Alternatively, you may use this file under the terms of the BSD license + ** as follows: + ** + ** "Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are + ** met: + ** * Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** * Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in + ** the documentation and/or other materials provided with the + ** distribution. + ** * Neither the name of The Qt Company Ltd nor the names of its + ** contributors may be used to endorse or promote products derived + ** from this software without specific prior written permission. + ** + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include <QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QOpenGLWindow> +#include <QOpenGLTexture> +#include <QOpenGLTextureBlitter> +#include <QPainter> +#include <QMouseEvent> +#include <QPlatformSurfaceEvent> + +#include <QtWaylandClient/private/qwaylanddisplay_p.h> +#include <QtWaylandClient/private/qwaylandintegration_p.h> +#include <QtWaylandClient/private/qwaylandserverbufferintegration_p.h> +#include "texturesharingextension.h" + +#include <QDebug> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QTimer> +#include <QMap> + +class TestWindow : public QOpenGLWindow +{ + Q_OBJECT + +public: + TestWindow() + : m_extension(nullptr) + { + m_extension = new TextureSharingExtension; + connect(m_extension, SIGNAL(bufferReceived(QtWaylandClient::QWaylandServerBuffer*, const QString&)), this, SLOT(receiveBuffer(QtWaylandClient::QWaylandServerBuffer*, const QString&))); + connect(m_extension, &TextureSharingExtension::activeChanged, this, &TestWindow::handleExtensionActive); + } + +public slots: + void receiveBuffer(QtWaylandClient::QWaylandServerBuffer *buffer, const QString &key) + { + if (!buffer) { + qWarning() << "Could not find image with key" << key; + return; + } + m_buffers.insert(key, buffer); + update(); + } + + + void handleExtensionActive() + { + if (m_extension->isActive()) + getImage("qt_logo"); + } + +protected: + + void mousePressEvent(QMouseEvent *ev) override { + QRect rect(10, height() - 10 - 50, 50, 50); + bool rectPressed = rect.contains(ev->pos()); + + static int c; + + if (rectPressed && ev->button() == Qt::LeftButton) + getImage(QString("unreasonably large image %1").arg(c++)); + else if (ev->button() == Qt::RightButton) + getImage("guitar.jpg"); + else if (ev->button() == Qt::MiddleButton) + unloadImageAt(ev->pos()); + } + + void initializeGL() override + { + m_blitter = new QOpenGLTextureBlitter; + m_blitter->create(); + } + + void paintGL() override { + glClearColor(.5, .45, .42, 1.); + glClear(GL_COLOR_BUFFER_BIT); + + // draw a "button" to click in + glScissor(10,10,50,50); + glEnable(GL_SCISSOR_TEST); + glClearColor(0.4, 0.7, 0.9, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + + int x = 0; + qDebug() << "*** paintGL ***"; + showBuffers(); + for (auto buffer: qAsConst(m_buffers)) { + m_blitter->bind(); + QSize s(buffer->size()); + qDebug() << "painting" << buffer << s; + if (s.width() > 1024) { + qDebug() << "showing large buffer at reduced size"; + s = QSize(128,128); + } + QRectF targetRect(QPointF(x,0), s); + QOpenGLTexture *texture = buffer->toOpenGlTexture(); + if (!texture) { + qWarning("Null texture"); + continue; + } + auto surfaceOrigin = QOpenGLTextureBlitter::OriginTopLeft; + QMatrix4x4 targetTransform = QOpenGLTextureBlitter::targetTransform(targetRect, QRect(QPoint(), size())); + m_blitter->blit(texture->textureId(), targetTransform, surfaceOrigin); + m_blitter->release(); + x += s.width() + 10; + } + } + +private: + void getImage(const QString &key) { + if (!m_buffers.contains(key)) + m_extension->requestImage(key); + } + + void showBuffers() const + { + auto end = m_buffers.cend(); + for (auto it = m_buffers.cbegin(); it != end; ++it) { + qDebug() << " " << it.key() << it.value(); + } + } + + void unloadImageAt(const QPoint &pos) { + int x = 0; + QtWaylandClient::QWaylandServerBuffer *foundBuffer = nullptr; + QString name; + auto end = m_buffers.cend(); + for (auto it = m_buffers.cbegin(); it != end; ++it) { + auto *buffer = it.value(); + QSize s(buffer->size()); + if (s.width() > 1024) + s = QSize(128,128); + QRectF targetRect(QPointF(x,0), s); + //qDebug() << " " << it.key() << it.value() << targetRect << pos; + + if (targetRect.contains(pos)) { + foundBuffer = buffer; + name = it.key(); + //qDebug() << "FOUND!!"; + break; + } + + x += s.width() + 10; + } + if (foundBuffer) { + qDebug() << "unloading image" << name << "found at" << pos; + unloadImage(name); + } else { + qDebug() << "no image at" << pos; + } + } + + void unloadImage(const QString &key) { + auto *buf = m_buffers.take(key); + if (buf) { + qDebug() << "unloadImage deleting" << buf; + delete buf; + m_extension->abandonImage(key); + } + update(); + } + + QOpenGLTextureBlitter *m_blitter = nullptr; + TextureSharingExtension *m_extension = nullptr; + QMap<QString, QtWaylandClient::QWaylandServerBuffer*> m_buffers; + +}; + +int main (int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + TestWindow window; + window.show(); + + return app.exec(); +} + +#include "main.moc" |