summaryrefslogtreecommitdiffstats
path: root/tests/auto/qml
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qml')
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/windowproperties/CMakeLists.txt13
-rw-r--r--tests/auto/qml/windowproperties/am-config.yaml8
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-native/CMakeLists.txt26
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-native/info.yaml6
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2.cpp127
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2_p.h45
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-native/wp-app.cpp33
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/info.yaml6
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/wp-app.qml23
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-qml/info.yaml6
-rw-r--r--tests/auto/qml/windowproperties/apps/wp-app-qml/wp-app.qml23
-rw-r--r--tests/auto/qml/windowproperties/tst_windowproperties.qml66
13 files changed, 383 insertions, 0 deletions
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt
index 6da3dcee..b89a6b1d 100644
--- a/tests/auto/qml/CMakeLists.txt
+++ b/tests/auto/qml/CMakeLists.txt
@@ -22,6 +22,7 @@ add_subdirectory(resources)
add_subdirectory(keyinput)
add_subdirectory(monitoring)
add_subdirectory(notifications)
+add_subdirectory(windowproperties)
if (QT_FEATURE_am_multi_process)
add_subdirectory(crash)
add_subdirectory(processtitle)
diff --git a/tests/auto/qml/windowproperties/CMakeLists.txt b/tests/auto/qml/windowproperties/CMakeLists.txt
new file mode 100644
index 00000000..d3fa1b87
--- /dev/null
+++ b/tests/auto/qml/windowproperties/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+qt_am_internal_add_qml_test(tst_windowproperties
+ CONFIG_YAML am-config.yaml
+ EXTRA_FILES apps
+ TEST_FILE tst_windowproperties.qml
+ CONFIGURATIONS
+ CONFIG NAME single-process ARGS --force-single-process
+ CONFIG NAME multi-process CONDITION QT_FEATURE_am_multi_process ARGS --force-multi-process
+)
+
+if (QT_FEATURE_am_multi_process)
+ add_subdirectory(apps/wp-app-native)
+endif()
diff --git a/tests/auto/qml/windowproperties/am-config.yaml b/tests/auto/qml/windowproperties/am-config.yaml
new file mode 100644
index 00000000..61b2bfb2
--- /dev/null
+++ b/tests/auto/qml/windowproperties/am-config.yaml
@@ -0,0 +1,8 @@
+formatVersion: 1
+formatType: am-configuration
+---
+applications:
+ builtinAppsManifestDir: "${CONFIG_PWD}/apps"
+
+flags:
+ noUiWatchdog: yes
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-native/CMakeLists.txt b/tests/auto/qml/windowproperties/apps/wp-app-native/CMakeLists.txt
new file mode 100644
index 00000000..8bc20ddf
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-native/CMakeLists.txt
@@ -0,0 +1,26 @@
+
+qt_internal_add_executable(wp-app-native
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
+ SOURCES
+ wp-app.cpp
+ waylandqtamclientextension_v2.cpp
+ waylandqtamclientextension_v2_p.h
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Quick
+ Qt::Qml
+ Qt::WaylandClient
+ Qt::WaylandClientPrivate
+)
+
+qt_internal_set_exceptions_flags(wp-app-native TRUE)
+
+qt_autogen_tools_initial_setup(wp-app-native)
+
+qt6_generate_wayland_protocol_client_sources(wp-app-native
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../src/wayland-extensions/qtam-extension.xml
+)
+
+qt_internal_add_autogen_sync_header_dependencies(wp-app-native WaylandClient)
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-native/info.yaml b/tests/auto/qml/windowproperties/apps/wp-app-native/info.yaml
new file mode 100644
index 00000000..c145f0c1
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-native/info.yaml
@@ -0,0 +1,6 @@
+formatVersion: 1
+formatType: am-application
+---
+id: 'wp-app-native'
+code: 'wp-app-native'
+runtime: 'native'
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2.cpp b/tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2.cpp
new file mode 100644
index 00000000..62f122da
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2.cpp
@@ -0,0 +1,127 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "waylandqtamclientextension_v2_p.h"
+
+#include <QWindow>
+#include <QGuiApplication>
+#include <QEvent>
+#include <QExposeEvent>
+#include <qpa/qplatformnativeinterface.h>
+#include <QJSValue>
+#include <QCborValue>
+
+
+// This is a copy of the built-in class WaylandQtAMClientExtension (version 1). Not all
+// functionality is actually used for the test, but it makes it easier to keep the code in sync
+// this way, as they share ~90% of the implementation.
+
+WaylandQtAMClientExtensionV2::WaylandQtAMClientExtensionV2()
+ : QWaylandClientExtensionTemplate(2)
+{
+ qApp->installEventFilter(this);
+}
+
+WaylandQtAMClientExtensionV2::~WaylandQtAMClientExtensionV2()
+{
+ qApp->removeEventFilter(this);
+}
+
+bool WaylandQtAMClientExtensionV2::eventFilter(QObject *o, QEvent *e)
+{
+ if (e->type() == QEvent::Expose) {
+ if (!isActive()) {
+ qWarning() << "WaylandQtAMClientExtensionV2 is not active";
+ } else {
+ QWindow *window = qobject_cast<QWindow *>(o);
+ Q_ASSERT(window);
+
+ // we're only interested in the first expose to setup our mapping
+ if (!m_windowToSurface.contains(window)) {
+ auto surface = static_cast<struct ::wl_surface *>
+ (QGuiApplication::platformNativeInterface()->nativeResourceForWindow("surface", window));
+ if (surface) {
+ m_windowToSurface.insert(window, surface);
+ const QVariantMap wp = windowProperties(window);
+ for (auto it = wp.cbegin(); it != wp.cend(); ++it)
+ sendPropertyToServer(surface, it.key(), it.value());
+ }
+ // pointers can be reused, so we have to remove the old mappings
+ connect(window, &QObject::destroyed, this, [this, window]() {
+ m_windowToSurface.remove(window);
+ m_windowProperties.remove(window);
+ });
+ }
+ }
+ } else if (e->type() == QEvent::Hide) {
+ m_windowToSurface.remove(qobject_cast<QWindow *>(o));
+ }
+
+ return QWaylandClientExtensionTemplate<WaylandQtAMClientExtensionV2>::eventFilter(o, e);
+}
+
+QVariantMap WaylandQtAMClientExtensionV2::windowProperties(QWindow *window) const
+{
+ return m_windowProperties.value(window);
+}
+
+void WaylandQtAMClientExtensionV2::sendPropertyToServer(struct ::wl_surface *surface, const QString &name,
+ const QVariant &value)
+{
+ if (int(qtam_extension::version()) != QWaylandClientExtension::version()) {
+ qWarning() << "Unsupported qtam_extension version:" << qtam_extension::version();
+ return;
+ }
+ const QByteArray data = QCborValue::fromVariant(value).toCbor();
+ set_window_property(surface, name, data);
+}
+
+bool WaylandQtAMClientExtensionV2::setWindowProperty(QWindow *window, const QString &name, const QVariant &value)
+{
+ if (setWindowPropertyHelper(window, name, value) && m_windowToSurface.contains(window)) {
+ auto surface = static_cast<struct ::wl_surface *>
+ (QGuiApplication::platformNativeInterface()->nativeResourceForWindow("surface", window));
+ if (surface) {
+ sendPropertyToServer(surface, name, value);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WaylandQtAMClientExtensionV2::setWindowPropertyHelper(QWindow *window, const QString &name, const QVariant &value)
+{
+ auto it = m_windowProperties.find(window);
+ if ((it == m_windowProperties.end()) || (it.value().value(name) != value)) {
+ if (it == m_windowProperties.end())
+ m_windowProperties[window].insert(name, value);
+ else
+ it.value().insert(name, value);
+
+ emit windowPropertyChanged(window, name, value);
+ return true;
+ }
+ return false;
+}
+
+void WaylandQtAMClientExtensionV2::clearWindowPropertyCache(QWindow *window)
+{
+ m_windowProperties.remove(window);
+}
+
+void WaylandQtAMClientExtensionV2::qtam_extension_window_property_changed(wl_surface *surface, const QString &name,
+ wl_array *value)
+{
+ if (int(qtam_extension::version()) != QWaylandClientExtension::version()) {
+ qWarning() << "Unsupported qtam_extension version:" << qtam_extension::version();
+ return;
+ }
+
+ if (QWindow *window = m_windowToSurface.key(surface)) {
+ const auto data = QByteArray::fromRawData(static_cast<const char *>(value->data), qsizetype(value->size));
+ const QVariant variantValue = QCborValue::fromCbor(data).toVariant();
+ setWindowPropertyHelper(window, name, variantValue);
+ }
+}
+
+#include "moc_waylandqtamclientextension_v2_p.cpp"
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2_p.h b/tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2_p.h
new file mode 100644
index 00000000..04251073
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-native/waylandqtamclientextension_v2_p.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef WAYLANDQTAMCLIENTEXTENSION_V2_P_H
+#define WAYLANDQTAMCLIENTEXTENSION_V2_P_H
+
+#include <QVariantMap>
+#include <QtWaylandClient/QWaylandClientExtensionTemplate>
+#include "qwayland-qtam-extension.h"
+
+QT_FORWARD_DECLARE_CLASS(QWindow)
+
+// This is a copy of the built-in class WaylandQtAMClientExtension (version 1). Not all
+// functionality is actually used for the test, but it makes it easier to keep the code in sync
+// this way, as they share ~90% of the implementation.
+
+class WaylandQtAMClientExtensionV2 : public QWaylandClientExtensionTemplate<WaylandQtAMClientExtensionV2>,
+ public ::QtWayland::qtam_extension
+{
+ Q_OBJECT
+
+public:
+ WaylandQtAMClientExtensionV2();
+ ~WaylandQtAMClientExtensionV2() override;
+
+ QVariantMap windowProperties(QWindow *window) const;
+ bool setWindowProperty(QWindow *window, const QString &name, const QVariant &value);
+ void clearWindowPropertyCache(QWindow *window);
+
+signals:
+ void windowPropertyChanged(QWindow *window, const QString &name, const QVariant &value);
+
+protected:
+ bool eventFilter(QObject *o, QEvent *e) override;
+
+private:
+ bool setWindowPropertyHelper(QWindow *window, const QString &name, const QVariant &value);
+ void sendPropertyToServer(::wl_surface *surface, const QString &name, const QVariant &value);
+ void qtam_extension_window_property_changed(wl_surface *surface, const QString &name, wl_array *value) override;
+
+ QMap<QWindow *, QVariantMap> m_windowProperties; // AXIVION Line Qt-QMapWithPointerKey: cleared on destroyed signal
+ QMap<QWindow *, ::wl_surface *> m_windowToSurface; // AXIVION Line Qt-QMapWithPointerKey: cleared on destroyed signal
+};
+
+#endif // WAYLANDQTAMCLIENTEXTENSION_V2_P_H
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-native/wp-app.cpp b/tests/auto/qml/windowproperties/apps/wp-app-native/wp-app.cpp
new file mode 100644
index 00000000..207aef6b
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-native/wp-app.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QGuiApplication>
+#include <QQuickView>
+
+#include "waylandqtamclientextension_v2_p.h"
+
+using namespace Qt::StringLiterals;
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+ auto qtamExtension = new WaylandQtAMClientExtensionV2();
+ QQuickView w;
+ w.setGeometry(0, 0, 100, 100);
+ w.show();
+
+ qsizetype expectedSize = 0;
+
+ const auto args = app.arguments();
+ if (auto pos = args.indexOf(u"--start-argument"_s); pos >= 0)
+ expectedSize = args.at(pos + 1).toInt();
+
+ QObject::connect(qtamExtension, &WaylandQtAMClientExtensionV2::windowPropertyChanged,
+ &app, [&](QWindow *, const QString &, const QVariant &) {
+ auto allProperties = qtamExtension->windowProperties(&w);
+ if (allProperties.size() == expectedSize)
+ qtamExtension->setWindowProperty(&w, u"BACKCHANNEL"_s, allProperties);
+ });
+
+ return app.exec();
+}
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/info.yaml b/tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/info.yaml
new file mode 100644
index 00000000..68c9447e
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/info.yaml
@@ -0,0 +1,6 @@
+formatVersion: 1
+formatType: am-application
+---
+id: 'wp-app-qml-inprocess'
+code: '../wp-app-qml/wp-app.qml'
+runtime: 'qml-inprocess'
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/wp-app.qml b/tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/wp-app.qml
new file mode 100644
index 00000000..e076ba34
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-qml-inprocess/wp-app.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtApplicationManager.Application
+import QtQuick
+
+ApplicationManagerWindow {
+ id: root
+ property int expectedSize: 0
+
+ Connections {
+ target: ApplicationInterface
+ function onOpenDocument(str) {
+ expectedSize = str
+ }
+ }
+
+ onWindowPropertyChanged: {
+ const allProperties = windowProperties()
+ if (Object.keys(allProperties).length === expectedSize)
+ setWindowProperty("BACKCHANNEL", allProperties)
+ }
+}
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-qml/info.yaml b/tests/auto/qml/windowproperties/apps/wp-app-qml/info.yaml
new file mode 100644
index 00000000..cc20adbc
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-qml/info.yaml
@@ -0,0 +1,6 @@
+formatVersion: 1
+formatType: am-application
+---
+id: 'wp-app-qml'
+code: 'wp-app.qml'
+runtime: 'qml'
diff --git a/tests/auto/qml/windowproperties/apps/wp-app-qml/wp-app.qml b/tests/auto/qml/windowproperties/apps/wp-app-qml/wp-app.qml
new file mode 100644
index 00000000..e076ba34
--- /dev/null
+++ b/tests/auto/qml/windowproperties/apps/wp-app-qml/wp-app.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtApplicationManager.Application
+import QtQuick
+
+ApplicationManagerWindow {
+ id: root
+ property int expectedSize: 0
+
+ Connections {
+ target: ApplicationInterface
+ function onOpenDocument(str) {
+ expectedSize = str
+ }
+ }
+
+ onWindowPropertyChanged: {
+ const allProperties = windowProperties()
+ if (Object.keys(allProperties).length === expectedSize)
+ setWindowProperty("BACKCHANNEL", allProperties)
+ }
+}
diff --git a/tests/auto/qml/windowproperties/tst_windowproperties.qml b/tests/auto/qml/windowproperties/tst_windowproperties.qml
new file mode 100644
index 00000000..3c05d453
--- /dev/null
+++ b/tests/auto/qml/windowproperties/tst_windowproperties.qml
@@ -0,0 +1,66 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtTest
+import QtApplicationManager.SystemUI
+import QtApplicationManager.Test
+
+TestCase {
+ id: testCase
+ when: windowShown
+ name: "Native"
+
+ WindowItem {
+ id: windowItem
+ }
+
+ property int timeout: 5000 * AmTest.timeoutFactor
+
+ function test_waylandExtension_data() {
+ let data = [
+ { tag: "qml-inprocess", appId: "wp-app-qml-inprocess" },
+ ]
+ if (!ApplicationManager.singleProcess) {
+ data.push({ tag: "qml", appId: "wp-app-qml" },
+ { tag: "native", appId: "wp-app-native" })
+ }
+ return data
+ }
+
+ function test_waylandExtension(data) {
+ let app = ApplicationManager.application(data.appId);
+
+ WindowManager.windowAdded.connect(function(window) {
+ windowItem.window = window
+ })
+ // keep this sorted alphabetically ... otherwise the JSON.stringify comparison will fail
+ let props = {
+ "ARRAY": [ 1, 2, "3" ],
+ "COLOR": Qt.color("blue"),
+ "DATE": new Date(),
+ "INT": 42,
+ "OBJECT": { "a": 1, "b": 2 },
+ "STRING": "str"
+ }
+
+ if (data.tag === "native") {
+ // cannot serialize QColor via CBOR
+ props["COLOR"] = props["COLOR"].toString()
+ }
+
+ verify(app.start("" + Object.keys(props).length))
+ tryVerify(() => { return app.runState === Am.Running }, timeout)
+ tryVerify(() => { return windowItem.window !== null }, timeout)
+ for (const prop in props)
+ windowItem.window.setWindowProperty(prop, props[prop])
+
+ tryVerify(() => {
+ return JSON.stringify(windowItem.window.windowProperty("BACKCHANNEL"))
+ === JSON.stringify(props)
+ }, timeout)
+ app.stop();
+ tryVerify(() => { return app.runState === Am.NotRunning }, timeout)
+ windowItem.window = null
+ }
+}