diff options
Diffstat (limited to 'tests/auto/qml')
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 + } +} |