diff options
Diffstat (limited to 'tests')
207 files changed, 13475 insertions, 4708 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..fd2887624 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from tests.pro. + +# special case begin +# TODO: Prepare for removal, once Platform brings in Threads. +if(NOT TARGET Threads::Threads) + qt_find_package(Threads REQUIRED) +endif() +# special case end + +if(QT_BUILD_STANDALONE_TESTS) + # Add qt_find_package calls for extra dependencies that need to be found when building + # the standalone tests here. + # special case begin + qt_find_package(Qt6 ${PROJECT_VERSION} OPTIONAL_COMPONENTS WaylandCompositor WaylandClient) + + if (NOT Qt6WaylandScannerTools_FOUND) + message(WARNING "QtWaylandTests is missing required components, nothing will be built. \ + Although this could be considered an error, the configuration will still pass as coin (Qt's \ + continuous integration system) will fail the build if configure fails, but will still try to \ + configure the module on targets that are missing dependencies.") + return() + endif() + # special case end +endif() +qt_build_tests() diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt new file mode 100644 index 000000000..87e83b552 --- /dev/null +++ b/tests/auto/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from auto.pro. + +if(TARGET Qt::WaylandClient) + add_subdirectory(client) +endif() +if(TARGET Qt::WaylandClient AND TARGET Qt::WaylandCompositor) + add_subdirectory(cmake) +endif() +if(TARGET Qt::WaylandCompositor) + add_subdirectory(compositor) +endif() diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro deleted file mode 100644 index 79ad29bd5..000000000 --- a/tests/auto/auto.pro +++ /dev/null @@ -1,7 +0,0 @@ -TEMPLATE=subdirs -QT_FOR_CONFIG += waylandclient-private - -qtConfig(wayland-client): \ - SUBDIRS += client cmake -qtHaveModule(waylandcompositor): \ - SUBDIRS += compositor diff --git a/tests/auto/client/CMakeLists.txt b/tests/auto/client/CMakeLists.txt new file mode 100644 index 000000000..79bcd442e --- /dev/null +++ b/tests/auto/client/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from client.pro. + +add_subdirectory(shared) + +# webOS has a modified version of QtWayland and does not support e.g. multiple window creation +# in a single client, attempting to do so will cause a segmentation fault +if (NOT WEBOS) + add_subdirectory(client) + add_subdirectory(clientextension) + add_subdirectory(cursor) + add_subdirectory(datadevicev1) + add_subdirectory(fullscreenshellv1) + add_subdirectory(iviapplication) + add_subdirectory(nooutput) + add_subdirectory(output) + add_subdirectory(primaryselectionv1) + add_subdirectory(reconnect) + add_subdirectory(seatv4) + add_subdirectory(seatv7) + add_subdirectory(seat) + add_subdirectory(surface) + add_subdirectory(tabletv2) + add_subdirectory(wl_connect) + add_subdirectory(xdgdecorationv1) + add_subdirectory(xdgoutput) + add_subdirectory(xdgshell) + add_subdirectory(scaling) +endif() +add_subdirectory(multithreaded) + +if(QT_FEATURE_im) + add_subdirectory(inputcontext) +endif() diff --git a/tests/auto/client/client.pro b/tests/auto/client/client.pro deleted file mode 100644 index 9fd8fc3f9..000000000 --- a/tests/auto/client/client.pro +++ /dev/null @@ -1,7 +0,0 @@ -TEMPLATE=subdirs - -SUBDIRS += \ - client \ - iviapplication \ - xdgshellv6 \ - wl_connect diff --git a/tests/auto/client/client/CMakeLists.txt b/tests/auto/client/client/CMakeLists.txt new file mode 100644 index 000000000..c6495eb6b --- /dev/null +++ b/tests/auto/client/client/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from client.pro. + +##################################################################### +## tst_client Test: +##################################################################### + +qt_internal_add_test(tst_client + SOURCES + tst_client.cpp + LIBRARIES + SharedClientTest +) + +#### Keys ignored in scope 1:.:.:client.pro:<TRUE>: +# check.commands = "$(TESTRUNNER)" "$${PWD}/run-with-all-shells.sh" "$(TESTARGS)" diff --git a/tests/auto/client/client/client.pro b/tests/auto/client/client/client.pro deleted file mode 100644 index f4ced252c..000000000 --- a/tests/auto/client/client/client.pro +++ /dev/null @@ -1,6 +0,0 @@ -include (../shared/shared.pri) - -TARGET = tst_client -SOURCES += tst_client.cpp - -check.commands = $(TESTRUNNER) $${PWD}/run-with-all-shells.sh $(TESTARGS) diff --git a/tests/auto/client/client/run-with-all-shells.sh b/tests/auto/client/client/run-with-all-shells.sh index 41f383900..5acd85a46 100755 --- a/tests/auto/client/client/run-with-all-shells.sh +++ b/tests/auto/client/client/run-with-all-shells.sh @@ -3,4 +3,3 @@ set -ex $@ env QT_WAYLAND_SHELL_INTEGRATION=wl-shell $@ env QT_WAYLAND_SHELL_INTEGRATION=ivi-shell $@ -env QT_WAYLAND_SHELL_INTEGRATION=xdg-shell-v6 $@ diff --git a/tests/auto/client/client/tst_client.cpp b/tests/auto/client/client/tst_client.cpp index 470db25a4..fa4a81e19 100644 --- a/tests/auto/client/client/tst_client.cpp +++ b/tests/auto/client/client/tst_client.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockcompositor.h" @@ -36,13 +11,29 @@ #include <QPixmap> #include <QDrag> #include <QWindow> +#if QT_CONFIG(opengl) #include <QOpenGLWindow> +#endif #include <QtTest/QtTest> #include <QtWaylandClient/private/qwaylandintegration_p.h> #include <QtGui/private/qguiapplication_p.h> -static const QSize screenSize(1600, 1200); +using namespace MockCompositor; + +constexpr int dataDeviceVersion = 1; + +class TestCompositor : public WlShellCompositor { +public: + explicit TestCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add<DataDeviceManager>(dataDeviceVersion); + }); + } + DataDevice *dataDevice() { return get<DataDeviceManager>()->deviceFor(get<Seat>()); } +}; class TestWindow : public QWindow { @@ -79,7 +70,7 @@ public: void mousePressEvent(QMouseEvent *event) override { ++mousePressEventCount; - mousePressPos = event->pos(); + mousePressPos = event->position().toPoint(); } void mouseReleaseEvent(QMouseEvent *) override @@ -107,6 +98,7 @@ public: QPoint mousePressPos; }; +#if QT_CONFIG(opengl) class TestGlWindow : public QOpenGLWindow { Q_OBJECT @@ -136,42 +128,16 @@ void TestGlWindow::paintGL() glClear(GL_COLOR_BUFFER_BIT); ++paintGLCalled; } +#endif // QT_CONFIG(opengl) -class tst_WaylandClient : public QObject +class tst_WaylandClient : public QObject, private TestCompositor { Q_OBJECT -public: - tst_WaylandClient(MockCompositor *c) - : compositor(c) - { - QSocketNotifier *notifier = new QSocketNotifier(compositor->waylandFileDescriptor(), QSocketNotifier::Read, this); - connect(notifier, SIGNAL(activated(int)), this, SLOT(processWaylandEvents())); - // connect to the event dispatcher to make sure to flush out the outgoing message queue - connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &tst_WaylandClient::processWaylandEvents); - connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &tst_WaylandClient::processWaylandEvents); - } - -public slots: - void processWaylandEvents() - { - compositor->processWaylandEvents(); - } - - void cleanup() - { - // make sure the surfaces from the last test are properly cleaned up - // and don't show up as false positives in the next test - QTRY_VERIFY(!compositor->surface()); - QTRY_VERIFY(!compositor->iviSurface()); - QTRY_VERIFY(!compositor->xdgToplevelV6()); - } private slots: - void primaryScreen(); - void screens(); - void addScreenWithGeometryChange(); - void windowScreens(); - void removePrimaryScreen(); + void cleanup() { + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } void createDestroyWindow(); void activeWindowFollowsKeyboardFocus(); void events(); @@ -181,133 +147,22 @@ private slots: void dontCrashOnMultipleCommits(); void hiddenTransientParent(); void hiddenPopupParent(); +#if QT_CONFIG(opengl) void glWindow(); +#endif // QT_CONFIG(opengl) void longWindowTitle(); - -private: - MockCompositor *compositor = nullptr; + void longWindowTitleWithUtf16Characters(); }; -void tst_WaylandClient::primaryScreen() -{ - compositor->setOutputMode(screenSize); - QTRY_COMPARE(QGuiApplication::primaryScreen()->size(), screenSize); -} - -void tst_WaylandClient::screens() -{ - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - compositor->sendAddOutput(); - QTRY_COMPARE(QGuiApplication::screens().size(), 2); - QSharedPointer<MockOutput> secondOutput; - QTRY_VERIFY(secondOutput = compositor->output(1)); - compositor->sendRemoveOutput(secondOutput); - QTRY_COMPARE(QGuiApplication::screens().size(), 1); -} - -//QTBUG-62044 -void tst_WaylandClient::addScreenWithGeometryChange() -{ - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - const QRect oldGeometry = QGuiApplication::primaryScreen()->geometry(); - compositor->sendAddOutput(); - - // Move the primary screen to the right - const QRect newGeometry(QPoint(screenSize.width(), 0), screenSize); - Q_ASSERT(oldGeometry != newGeometry); - compositor->sendOutputGeometry(compositor->output(0), newGeometry); - - QTRY_COMPARE(QGuiApplication::screens().size(), 2); - QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), newGeometry); - - compositor->sendRemoveOutput(compositor->output(1)); - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - - // Move the screen back - compositor->sendOutputGeometry(compositor->output(0), oldGeometry); - QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), oldGeometry); -} - -void tst_WaylandClient::windowScreens() -{ - QSharedPointer<MockOutput> firstOutput; - QTRY_VERIFY(firstOutput = compositor->output()); - - TestWindow window; - window.show(); - - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); - - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - QScreen *primaryScreen = QGuiApplication::screens().first(); - QCOMPARE(window.screen(), primaryScreen); - - compositor->sendAddOutput(); - - QTRY_COMPARE(QGuiApplication::screens().size(), 2); - QScreen *secondaryScreen = QGuiApplication::screens().at(1); - QVERIFY(secondaryScreen); - - window.setScreen(secondaryScreen); - QCOMPARE(window.screen(), secondaryScreen); - - QSharedPointer<MockOutput> secondOutput; - QTRY_VERIFY(secondOutput = compositor->output(1)); - compositor->sendSurfaceEnter(surface, firstOutput); - - compositor->sendSurfaceEnter(surface, secondOutput); - QTRY_COMPARE(window.screen(), primaryScreen); - - compositor->sendSurfaceLeave(surface, firstOutput); - QTRY_COMPARE(window.screen(), secondaryScreen); - - compositor->sendRemoveOutput(secondOutput); - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - QCOMPARE(window.screen(), primaryScreen); -} - -void tst_WaylandClient::removePrimaryScreen() -{ - QSharedPointer<MockOutput> firstOutput; - QTRY_VERIFY(firstOutput = compositor->output()); - - TestWindow window; - window.show(); - - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - QScreen *primaryScreen = QGuiApplication::screens().first(); - QCOMPARE(window.screen(), primaryScreen); - - compositor->sendAddOutput(); - - QTRY_COMPARE(QGuiApplication::screens().size(), 2); - QTRY_COMPARE(QGuiApplication::primaryScreen()->virtualSiblings().size(), 2); - QScreen *secondaryScreen = QGuiApplication::screens().at(1); - QVERIFY(secondaryScreen); - - compositor->sendRemoveOutput(firstOutput); - QTRY_COMPARE(QGuiApplication::screens().size(), 1); - - compositor->sendMousePress(surface, window.frameOffset() + QPoint(10, 10)); - QTRY_COMPARE(window.mousePressEventCount, 1); - compositor->sendMouseRelease(surface); - QTRY_COMPARE(window.mouseReleaseEventCount, 1); -} - void tst_WaylandClient::createDestroyWindow() { TestWindow window; window.show(); - QTRY_VERIFY(compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(surface()); window.destroy(); - QTRY_VERIFY(!compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(!surface()); } void tst_WaylandClient::activeWindowFollowsKeyboardFocus() @@ -315,24 +170,27 @@ void tst_WaylandClient::activeWindowFollowsKeyboardFocus() TestWindow window; window.show(); - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); - - QTRY_VERIFY(window.isExposed()); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); - if (compositor->xdgToplevelV6()) - QSKIP("On xdg-shell v6 focus is handled by configure events"); + QCOMPOSITOR_TRY_VERIFY(window.isExposed()); QCOMPARE(window.focusInEventCount, 0); - compositor->setKeyboardFocus(surface); + exec([&] { + keyboard()->sendEnter(s); + }); QTRY_COMPARE(window.focusInEventCount, 1); - QTRY_COMPARE(QGuiApplication::focusWindow(), &window); + QCOMPARE(QGuiApplication::focusWindow(), &window); QCOMPARE(window.focusOutEventCount, 0); - compositor->setKeyboardFocus(QSharedPointer<MockSurface>(nullptr)); + exec([&] { + keyboard()->sendLeave(s); // or implement setFocus in Keyboard + }); QTRY_COMPARE(window.focusOutEventCount, 1); - QTRY_COMPARE(QGuiApplication::focusWindow(), static_cast<QWindow *>(nullptr)); + QCOMPARE(QGuiApplication::focusWindow(), static_cast<QWindow *>(nullptr)); } void tst_WaylandClient::events() @@ -340,46 +198,78 @@ void tst_WaylandClient::events() TestWindow window; window.show(); - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); - QTRY_VERIFY(window.isExposed()); + QCOMPOSITOR_TRY_VERIFY(window.isExposed()); - compositor->setKeyboardFocus(surface); - QTRY_COMPARE(window.focusInEventCount, 1); - QTRY_COMPARE(QGuiApplication::focusWindow(), &window); + QCOMPARE(window.focusInEventCount, 0); + exec([&] { + keyboard()->sendEnter(s); + }); + QTRY_COMPARE(window.focusInEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), &window); + + // See also https://wayland.app/protocols/wayland#wl_keyboard:enum:keymap_format + // wl_keyboard::keymap_format + // keymap_format { no_keymap, xkb_v1 } + // Argument Value Description + // no_keymap 0 no keymap; client must understand how to interpret the raw keycode + // xkb_v1 1 libxkbcommon compatible; to determine the xkb keycode, clients must add 8 to the key event keycode uint keyCode = 80; // arbitrarily chosen QCOMPARE(window.keyPressEventCount, 0); - compositor->sendKeyPress(surface, keyCode); + exec([&] { + keyboard()->sendKey(client(), keyCode - 8, Keyboard::key_state_pressed); // related with native scan code + }); QTRY_COMPARE(window.keyPressEventCount, 1); - QTRY_COMPARE(window.keyCode, keyCode); + QCOMPARE(window.keyCode, keyCode); QCOMPARE(window.keyReleaseEventCount, 0); - compositor->sendKeyRelease(surface, keyCode); + exec([&] { + keyboard()->sendKey(client(), keyCode - 8, Keyboard::key_state_released); // related with native scan code + }); QTRY_COMPARE(window.keyReleaseEventCount, 1); QCOMPARE(window.keyCode, keyCode); const int touchId = 0; - compositor->sendTouchDown(surface, window.frameOffset() + QPoint(10, 10), touchId); + exec([&] { + touch()->sendDown(s, window.frameOffset() + QPoint(10, 10), touchId); + }); // Note: wl_touch.frame should not be the last event in a test until QTBUG-66563 is fixed. // See also: QTBUG-66537 - compositor->sendTouchFrame(surface); + exec([&] { + touch()->sendFrame(client()); + }); QTRY_COMPARE(window.touchEventCount, 1); - compositor->sendTouchUp(surface, touchId); - compositor->sendTouchFrame(surface); + exec([&] { + touch()->sendUp(client(), touchId); + touch()->sendFrame(client()); + }); QTRY_COMPARE(window.touchEventCount, 2); QPoint mousePressPos(16, 16); QCOMPARE(window.mousePressEventCount, 0); - compositor->sendMousePress(surface, window.frameOffset() + mousePressPos); + exec([&] { + pointer()->sendEnter(s, window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + pointer()->sendFrame(client()); + }); QTRY_COMPARE(window.mousePressEventCount, 1); QTRY_COMPARE(window.mousePressPos, mousePressPos); QCOMPARE(window.mouseReleaseEventCount, 0); - compositor->sendMouseRelease(surface); + exec([&] { + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + pointer()->sendFrame(client()); + }); QTRY_COMPARE(window.mouseReleaseEventCount, 1); } @@ -388,9 +278,11 @@ void tst_WaylandClient::backingStore() TestWindow window; window.show(); - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); QRect rect(QPoint(), window.size()); @@ -407,17 +299,17 @@ void tst_WaylandClient::backingStore() backingStore.endPaint(); - QVERIFY(surface->image.isNull()); + QVERIFY(s->m_image.isNull()); backingStore.flush(rect); - QTRY_COMPARE(surface->image.size(), window.frameGeometry().size()); - QTRY_COMPARE(surface->image.pixel(window.frameMargins().left(), window.frameMargins().top()), color.rgba()); + QTRY_COMPARE(s->m_image.size(), window.frameGeometry().size()); + QTRY_COMPARE(s->m_image.pixel(window.frameMargins().left(), window.frameMargins().top()), color.rgba()); window.hide(); // hiding the window should destroy the surface - QTRY_VERIFY(!compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(!surface()); } class DndWindow : public QWindow @@ -456,29 +348,76 @@ private: QPixmap m_dragIcon; }; +class DNDTest : public QObject +{ + Q_OBJECT + +public: + DNDTest(QObject *parent = nullptr) + : QObject(parent) {} + + Surface *m_surface = nullptr; + TestCompositor *m_compositor = nullptr; + QPoint m_frameOffset; + +public slots: + void finishMouseDrag(); + void touchDrag(); +}; + +void DNDTest::finishMouseDrag() +{ + m_compositor->exec([&] { + m_compositor->dataDevice()->sendDrop(m_surface); + m_compositor->dataDevice()->sendLeave(m_surface); + }); +} + +void DNDTest::touchDrag() +{ + m_compositor->exec([&] { + m_compositor->dataDevice()->sendDataOffer(m_surface->resource()->client()); + m_compositor->dataDevice()->sendEnter(m_surface, m_frameOffset + QPoint(20, 20)); + m_compositor->dataDevice()->sendMotion(m_surface, m_frameOffset + QPoint(21, 21)); + m_compositor->dataDevice()->sendDrop(m_surface); + m_compositor->dataDevice()->sendLeave(m_surface); + }); +} + void tst_WaylandClient::touchDrag() { DndWindow window; window.show(); - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); - - compositor->setKeyboardFocus(surface); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + DNDTest test; + test.m_surface = s; + test.m_compositor = this; + test.m_frameOffset = window.frameOffset(); + + exec([&] { + QObject::connect(dataDevice(), &DataDevice::dragStarted, + &test, &DNDTest::touchDrag); + }); + + exec([&] { + keyboard()->sendEnter(s); + }); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); - const int id = 0; - compositor->sendTouchDown(surface, window.frameOffset() + QPoint(10, 10), id); - compositor->sendTouchFrame(surface); - compositor->sendTouchMotion(surface, window.frameOffset() + QPoint(20, 20), id); - compositor->sendTouchFrame(surface); - compositor->waitForStartDrag(); - compositor->sendDataDeviceDataOffer(surface); - compositor->sendDataDeviceEnter(surface, window.frameOffset() + QPoint(20, 20)); - compositor->sendDataDeviceMotion(window.frameOffset() + QPoint(21, 21)); - compositor->sendDataDeviceDrop(surface); - compositor->sendDataDeviceLeave(surface); + const int touchId = 0; + exec([&] { + touch()->sendDown(s, window.frameOffset() + QPoint(10, 10), touchId); + touch()->sendFrame(client()); + touch()->sendMotion(client(), window.frameOffset() + QPoint(20, 20), touchId); + touch()->sendFrame(client()); + }); + QTRY_VERIFY(window.dragStarted); } @@ -487,26 +426,45 @@ void tst_WaylandClient::mouseDrag() DndWindow window; window.show(); - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + DNDTest test; + test.m_surface = s; + test.m_compositor = this; + + exec([&] { + QObject::connect(dataDevice(), &DataDevice::dragStarted, + &test, &DNDTest::finishMouseDrag); + }); - compositor->setKeyboardFocus(surface); + exec([&] { + keyboard()->sendEnter(s); + }); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); - compositor->sendMousePress(surface, window.frameOffset() + QPoint(10, 10)); - compositor->sendDataDeviceDataOffer(surface); - compositor->sendDataDeviceEnter(surface, window.frameOffset() + QPoint(20, 20)); - compositor->sendDataDeviceMotion(window.frameOffset() + QPoint(21, 21)); - compositor->waitForStartDrag(); - compositor->sendDataDeviceDrop(surface); - compositor->sendDataDeviceLeave(surface); + QPoint mousePressPos(16, 16); + exec([&] { + pointer()->sendEnter(s, window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + pointer()->sendFrame(client()); + + dataDevice()->sendDataOffer(s->resource()->client()); + dataDevice()->sendEnter(s, window.frameOffset() + QPoint(20, 20)); + dataDevice()->sendMotion(s, window.frameOffset() + QPoint(21, 21)); + }); + QTRY_VERIFY(window.dragStarted); } void tst_WaylandClient::dontCrashOnMultipleCommits() { - QSKIP("This test is flaky. See QTBUG-68756."); auto window = new TestWindow(); window->show(); @@ -525,10 +483,11 @@ void tst_WaylandClient::dontCrashOnMultipleCommits() backingStore.flush(rect); backingStore.flush(rect); - compositor->processWaylandEvents(); + QCOMPOSITOR_TRY_VERIFY(surface()); } delete window; + QCOMPOSITOR_TRY_VERIFY(!surface()); } void tst_WaylandClient::hiddenTransientParent() @@ -539,15 +498,14 @@ void tst_WaylandClient::hiddenTransientParent() transient.setTransientParent(&parent); parent.show(); - QTRY_VERIFY(compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(surface()); parent.hide(); - QTRY_VERIFY(!compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(!surface()); transient.show(); - QTRY_VERIFY(compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(surface()); } - void tst_WaylandClient::hiddenPopupParent() { TestWindow toplevel; @@ -555,12 +513,23 @@ void tst_WaylandClient::hiddenPopupParent() // wl_shell relies on a mouse event in order to send a serial and seat // with the set_popup request. - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + QCOMPOSITOR_TRY_VERIFY(toplevel.isExposed()); + QPoint mousePressPos(16, 16); QCOMPARE(toplevel.mousePressEventCount, 0); - compositor->sendMousePress(surface, toplevel.frameOffset() + mousePressPos); + exec([&] { + pointer()->sendEnter(s, toplevel.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendMotion(client(), toplevel.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + pointer()->sendFrame(client()); + }); QTRY_COMPARE(toplevel.mousePressEventCount, 1); QWindow popup; @@ -568,21 +537,24 @@ void tst_WaylandClient::hiddenPopupParent() popup.setFlag(Qt::Popup, true); toplevel.hide(); - QTRY_VERIFY(!compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(!surface()); popup.show(); - QTRY_VERIFY(compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(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"); QScopedPointer<TestGlWindow> testWindow(new TestGlWindow); testWindow->show(); - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = compositor->surface()); - compositor->sendShellSurfaceConfigure(surface); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); QTRY_COMPARE(testWindow->paintGLCalled, 1); @@ -597,8 +569,9 @@ void tst_WaylandClient::glWindow() //confirm we don't crash when we delete an already hidden GL window //QTBUG-65553 testWindow->setVisible(false); - QTRY_VERIFY(!compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(!surface()); } +#endif // QT_CONFIG(opengl) void tst_WaylandClient::longWindowTitle() { @@ -607,29 +580,33 @@ void tst_WaylandClient::longWindowTitle() QString absurdlyLongTitle(10000, QLatin1Char('z')); window.setTitle(absurdlyLongTitle); window.show(); - QTRY_VERIFY(compositor->surface()); + QCOMPOSITOR_TRY_VERIFY(surface()); +} + +void tst_WaylandClient::longWindowTitleWithUtf16Characters() +{ + QWindow window; + QString absurdlyLongTitle = QString("三").repeated(10000); + Q_ASSERT(absurdlyLongTitle.size() == 10000); // just making sure the test isn't broken + window.setTitle(absurdlyLongTitle); + window.show(); + QCOMPOSITOR_TRY_VERIFY(surface()); } int main(int argc, char **argv) { - setenv("XDG_RUNTIME_DIR", ".", 1); + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + QString shell = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_SHELL_INTEGRATION")); + if (shell.isEmpty()) + setenv("QT_WAYLAND_SHELL_INTEGRATION", "wl-shell", 1); - MockCompositor compositor; - compositor.setOutputMode(screenSize); - + tst_WaylandClient tc; QGuiApplication app(argc, argv); - - // Initializing some client buffer integrations (i.e. eglInitialize) may block while waiting - // for a wayland sync. So we call clientBufferIntegration prior to applicationInitialized - // (while the compositor processes events without waiting) in order to avoid hanging later. - auto *waylandIntegration = static_cast<QtWaylandClient::QWaylandIntegration *>(QGuiApplicationPrivate::platformIntegration()); - waylandIntegration->clientBufferIntegration(); - - compositor.applicationInitialized(); - - tst_WaylandClient tc(&compositor); + QTEST_SET_MAIN_SOURCE_PATH return QTest::qExec(&tc, argc, argv); } #include <tst_client.moc> + diff --git a/tests/auto/client/clientextension/CMakeLists.txt b/tests/auto/client/clientextension/CMakeLists.txt new file mode 100644 index 000000000..4997b7d77 --- /dev/null +++ b/tests/auto/client/clientextension/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_clientextension + SOURCES + tst_clientextension.cpp + LIBRARIES + SharedClientTest +) + +qt6_generate_wayland_protocol_client_sources(tst_clientextension + FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.xml +) + +qt6_generate_wayland_protocol_server_sources(tst_clientextension + FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.xml +) diff --git a/tests/auto/client/clientextension/test.xml b/tests/auto/client/clientextension/test.xml new file mode 100644 index 000000000..f8d5b4eac --- /dev/null +++ b/tests/auto/client/clientextension/test.xml @@ -0,0 +1,6 @@ +<protocol name="test"> + <interface name="test_interface" version="1"> + <request name="release" type="destructor" /> + </interface> +</protocol> + diff --git a/tests/auto/client/clientextension/tst_clientextension.cpp b/tests/auto/client/clientextension/tst_clientextension.cpp new file mode 100644 index 000000000..8dd4e0d98 --- /dev/null +++ b/tests/auto/client/clientextension/tst_clientextension.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2021 David Redondo <qt@david-redondo.de> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QSignalSpy> +#include <QtGui/private/qguiapplication_p.h> +#include <QtWaylandClient/private/qwayland-wayland.h> +#include <QtWaylandClient/private/qwaylanddisplay_p.h> +#include <QtWaylandClient/private/qwaylandintegration_p.h> +#include <QtWaylandClient/qwaylandclientextension.h> +#include <qwayland-server-test.h> +#include <qwayland-test.h> +#include "mockcompositor.h" +#include "coreprotocol.h" + +using namespace MockCompositor; + +class TestExtension + : public QWaylandClientExtensionTemplate<TestExtension, &QtWayland::test_interface::release>, + public QtWayland::test_interface +{ +public: + TestExtension() : QWaylandClientExtensionTemplate(1){}; + void initialize() { QWaylandClientExtension::initialize(); } +}; + +class TestGlobal : public Global, public QtWaylandServer::test_interface +{ + Q_OBJECT +public: + explicit TestGlobal(CoreCompositor *compositor) + : QtWaylandServer::test_interface(compositor->m_display, 1) + { + } +}; + +class tst_clientextension : public QObject, private CoreCompositor +{ + Q_OBJECT +private: + QtWaylandClient::QWaylandDisplay *display() + { + return static_cast<QtWaylandClient::QWaylandIntegration *>( + QGuiApplicationPrivate::platformIntegration()) + ->display(); + } +private slots: + void cleanup() + { + display()->flushRequests(); + dispatch(); + exec([this] { removeAll<TestGlobal>(); }); + QTRY_COMPARE(display()->globals().size(), 0); + } + void createWithoutGlobal(); + void createWithGlobalAutomatic(); + void createWithGlobalManual(); + void globalBecomesAvailable(); + void globalRemoved(); +}; + +void tst_clientextension::createWithoutGlobal() +{ + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + QVERIFY(!extension.isActive()); + QCOMPARE(spy.size(), 0); + extension.initialize(); + QVERIFY(!extension.isActive()); + QCOMPARE(spy.size(), 0); +} + +void tst_clientextension::createWithGlobalAutomatic() +{ + exec([this] { add<TestGlobal>(); }); + QTRY_COMPARE(display()->globals().size(), 1); + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + QTRY_VERIFY(extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +void tst_clientextension::createWithGlobalManual() +{ + exec([this] { add<TestGlobal>(); }); + QTRY_COMPARE(display()->globals().size(), 1); + // Wait for the display to have the global + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + extension.initialize(); + QVERIFY(extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +void tst_clientextension::globalBecomesAvailable() +{ + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + exec([this] { add<TestGlobal>(); }); + QTRY_VERIFY(extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +void tst_clientextension::globalRemoved() +{ + exec([this] { add<TestGlobal>(); }); + TestExtension extension; + QTRY_VERIFY(extension.isActive()); + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + QCOMPOSITOR_TRY_COMPARE(get<TestGlobal>()->resourceMap().size(), 1); + + exec([this] { removeAll<TestGlobal>(); }); + QTRY_VERIFY(!extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +int main(int argc, char **argv) +{ + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); + setenv("QT_QPA_PLATFORM", "wayland", 1); + setenv("QT_WAYLAND_DONT_CHECK_SHELL_INTEGRATION", "1", 1); + + tst_clientextension tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_clientextension.moc" diff --git a/tests/auto/client/cursor/CMakeLists.txt b/tests/auto/client/cursor/CMakeLists.txt new file mode 100644 index 000000000..a7814a2c2 --- /dev/null +++ b/tests/auto/client/cursor/CMakeLists.txt @@ -0,0 +1,11 @@ +##################################################################### +## tst_cursor Test: +##################################################################### + +qt_internal_add_test(tst_wayland_cursor + SOURCES + tst_cursor.cpp + cursorshapev1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/cursor/cursorshapev1.cpp b/tests/auto/client/cursor/cursorshapev1.cpp new file mode 100644 index 000000000..93750df95 --- /dev/null +++ b/tests/auto/client/cursor/cursorshapev1.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "cursorshapev1.h" + +namespace MockCompositor { + +CursorShapeManager::CursorShapeManager(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_cursor_shape_manager_v1(compositor->m_display, version) +{ +} + +void CursorShapeManager::wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t id, wl_resource *pointer) +{ + auto *p = fromResource<Pointer>(pointer); + auto *cursorShape = new CursorShapeDevice(p, resource->client(), id, resource->version()); + connect(cursorShape, &QObject::destroyed, this, [this, cursorShape]() { + m_cursorDevices.removeOne(cursorShape); + }); + m_cursorDevices << cursorShape; +} + +CursorShapeDevice::CursorShapeDevice(Pointer *pointer, wl_client *client, int id, int version) + : QtWaylandServer::wp_cursor_shape_device_v1(client, id, version) + , m_pointer(pointer) +{ +} + +void CursorShapeDevice::wp_cursor_shape_device_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void CursorShapeDevice::wp_cursor_shape_device_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void CursorShapeDevice::wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) +{ + Q_UNUSED(resource); + m_currentShape = static_cast<CursorShapeDevice::shape>(shape); + emit setCursor(serial); +} + +} diff --git a/tests/auto/client/cursor/cursorshapev1.h b/tests/auto/client/cursor/cursorshapev1.h new file mode 100644 index 000000000..0befc3223 --- /dev/null +++ b/tests/auto/client/cursor/cursorshapev1.h @@ -0,0 +1,44 @@ +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_CURSORSHAPE_H +#define MOCKCOMPOSITOR_CURSORSHAPE_H + +#include "coreprotocol.h" +#include <qwayland-server-cursor-shape-v1.h> + +namespace MockCompositor { + +class CursorShapeDevice; + +class CursorShapeManager : public Global, public QtWaylandServer::wp_cursor_shape_manager_v1 +{ + Q_OBJECT +public: + explicit CursorShapeManager(CoreCompositor *compositor, int version = 1); + QList<CursorShapeDevice *> m_cursorDevices; + +protected: + void wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t id, wl_resource *pointer) override; +}; + +class CursorShapeDevice : public QObject, public QtWaylandServer::wp_cursor_shape_device_v1 +{ + Q_OBJECT +public: + explicit CursorShapeDevice(Pointer *pointer, wl_client *client, int id, int version); + Pointer *m_pointer; + shape m_currentShape = shape_default; + +Q_SIGNALS: + void setCursor(uint serial); + +protected: + void wp_cursor_shape_device_v1_destroy_resource(Resource *resource) override; + void wp_cursor_shape_device_v1_destroy(Resource *resource) override; + void wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) override; +}; + +} + +#endif diff --git a/tests/auto/client/cursor/tst_cursor.cpp b/tests/auto/client/cursor/tst_cursor.cpp new file mode 100644 index 000000000..070e062f6 --- /dev/null +++ b/tests/auto/client/cursor/tst_cursor.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtWaylandClient/private/wayland-wayland-client-protocol.h> +#include <QtWaylandClient/private/qwaylandwindow_p.h> + +#include "cursorshapev1.h" + +using namespace MockCompositor; + +class tst_cursor : public QObject, private DefaultCompositor +{ + Q_OBJECT +public: + tst_cursor(); + CursorShapeDevice* cursorShape(); +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void setCursor(); +}; + +tst_cursor::tst_cursor() +{ + exec([this] { + m_config.autoConfigure = true; + add<CursorShapeManager>(1); + }); +} + +CursorShapeDevice* tst_cursor::cursorShape() +{ + auto manager = get<CursorShapeManager>(); + if (!manager->m_cursorDevices.count()) + return nullptr; + return manager->m_cursorDevices[0]; +} + +void tst_cursor::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_cursor::setCursor() +{ + QCOMPOSITOR_TRY_VERIFY(cursorShape()); + QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor); + QSignalSpy setCursorShapeSpy(exec([&] { return cursorShape(); }), &CursorShapeDevice::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, {32, 32}); + }); + setCursorShapeSpy.wait(); + // verify we got given a cursor on enter + QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_default); + QVERIFY(setCursorSpy.isEmpty()); + QCOMPARE(setCursorShapeSpy.takeFirst().at(0).toUInt(), enterSerial); + + // client sets a different shape + window.setCursor(QCursor(Qt::WaitCursor)); + QVERIFY(setCursorShapeSpy.wait()); + QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_wait); + + setCursorShapeSpy.clear(); + + // client hides the cursor + // CursorShape will not be used, instead, it uses the old path + window.setCursor(QCursor(Qt::BlankCursor)); + QVERIFY(setCursorSpy.wait()); + QVERIFY(setCursorShapeSpy.isEmpty()); + QCOMPOSITOR_VERIFY(!pointer()->cursorSurface()); + + // same for bitmaps + QPixmap myCustomPixmap(10, 10); + myCustomPixmap.fill(Qt::red); + window.setCursor(QCursor(myCustomPixmap)); + QVERIFY(setCursorSpy.wait()); + QVERIFY(setCursorShapeSpy.isEmpty()); + + // set a shape again + window.setCursor(QCursor(Qt::BusyCursor)); + QVERIFY(setCursorShapeSpy.wait()); + QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_progress); + + setCursorShapeSpy.clear(); + + // set the same bitmap again, make sure switching from new to old path works + // even if the bitmap cursor's properties haven't changed + window.setCursor(QCursor(myCustomPixmap)); + QVERIFY(setCursorSpy.wait()); + QVERIFY(setCursorShapeSpy.isEmpty()); +} + +QCOMPOSITOR_TEST_MAIN(tst_cursor) +#include "tst_cursor.moc" diff --git a/tests/auto/client/datadevicev1/CMakeLists.txt b/tests/auto/client/datadevicev1/CMakeLists.txt new file mode 100644 index 000000000..cfc2f5beb --- /dev/null +++ b/tests/auto/client/datadevicev1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from datadevicev1.pro. + +##################################################################### +## tst_datadevicev1 Test: +##################################################################### + +qt_internal_add_test(tst_datadevicev1 + SOURCES + tst_datadevicev1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp new file mode 100644 index 000000000..50d78130a --- /dev/null +++ b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp @@ -0,0 +1,323 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <QtGui/QRasterWindow> +#include <QtGui/QClipboard> +#include <QtGui/QDrag> + +using namespace MockCompositor; + +constexpr int dataDeviceVersion = 3; + +class DataDeviceCompositor : public DefaultCompositor { +public: + explicit DataDeviceCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add<DataDeviceManager>(dataDeviceVersion); + }); + } + DataDevice *dataDevice() { return get<DataDeviceManager>()->deviceFor(get<Seat>()); } +}; + +class tst_datadevicev1 : public QObject, private DataDeviceCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void initTestCase(); + void pasteAscii(); + void pasteUtf8(); + void pasteMozUrl(); + void pasteSingleUtf8MozUrl(); + void destroysPreviousSelection(); + void destroysSelectionWithSurface(); + void destroysSelectionOnLeave(); + void dragWithoutFocus(); +}; + +void tst_datadevicev1::initTestCase() +{ + QCOMPOSITOR_TRY_VERIFY(pointer()); + QCOMPOSITOR_TRY_VERIFY(keyboard()); + + QCOMPOSITOR_TRY_VERIFY(dataDevice()); + QCOMPOSITOR_TRY_VERIFY(dataDevice()->resourceMap().contains(client())); + QCOMPOSITOR_TRY_COMPARE(dataDevice()->resourceMap().value(client())->version(), dataDeviceVersion); +} + +void tst_datadevicev1::pasteAscii() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_text = QGuiApplication::clipboard()->text(); } + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/plain"}); + connect(offer, &DataOffer::receive, offer, [](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(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + QTRY_COMPARE(window.m_text, "normal ascii"); +} + +void tst_datadevicev1::pasteUtf8() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_text = QGuiApplication::clipboard()->text(); } + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/plain", "text/plain;charset=utf-8"}); + connect(offer, &DataOffer::receive, offer, [](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(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); +} + +void tst_datadevicev1::pasteMozUrl() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_urls = QGuiApplication::clipboard()->mimeData()->urls(); } + QList<QUrl> m_urls; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/x-moz-url"}); + connect(offer, &DataOffer::receive, offer, [](QString mimeType, int fd) { + QFile file; + file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); + QCOMPARE(mimeType, "text/x-moz-url"); + const QString content("https://www.qt.io/\nQt\nhttps://www.example.com/\nExample Website"); + // Need UTF-16. + file.write(reinterpret_cast<const char *>(content.data()), content.size() * 2); + file.close(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + + QTRY_COMPARE(window.m_urls.count(), 2); + QCOMPARE(window.m_urls.at(0), QUrl("https://www.qt.io/")); + QCOMPARE(window.m_urls.at(1), QUrl("https://www.example.com/")); +} + +void tst_datadevicev1::pasteSingleUtf8MozUrl() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_urls = QGuiApplication::clipboard()->mimeData()->urls(); } + QList<QUrl> m_urls; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/x-moz-url"}); + connect(offer, &DataOffer::receive, offer, [](QString mimeType, int fd) { + QFile file; + file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); + QCOMPARE(mimeType, "text/x-moz-url"); + const QString content("https://www.qt.io/"); + file.write(content.toUtf8()); + file.close(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + + QTRY_COMPARE(window.m_urls.count(), 1); + QCOMPARE(window.m_urls.at(0), QUrl("https://www.qt.io/")); +} + +void tst_datadevicev1::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([&] { + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 1); + }); + + exec([&] { + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 2); + }); + + // Verify the first offer gets destroyed + QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 1); + + exec([&] { + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + auto *surface = xdgSurface()->m_surface; + keyboard()->sendLeave(surface); + }); + + // Clients are required to destroy their offer when losing keyboard focus + QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); +} + +void tst_datadevicev1::destroysSelectionWithSurface() +{ + auto *window = new QRasterWindow; + 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([&] { + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 1); + }); + + // Ping to make sure we receive the wl_keyboard enter and leave events, before destroying the + // surface. Otherwise, the client will receive enter and leave events with a destroyed (null) + // surface, which is not what we are trying to test for here. + xdgPingAndWaitForPong(); + window->destroy(); + + QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); +} + +void tst_datadevicev1::destroysSelectionOnLeave() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + }); + + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard)); + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard)->hasText()); + + QSignalSpy dataChangedSpy(QGuiApplication::clipboard(), &QClipboard::dataChanged); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendLeave(surface); + }); + + QTRY_COMPARE(dataChangedSpy.size(), 1); + QVERIFY(!QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard)->hasText()); +} + +// The application should not crash if it attempts to start a drag operation +// when it doesn't have input focus (QTBUG-76368) +void tst_datadevicev1::dragWithoutFocus() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + auto *mimeData = new QMimeData; + const QByteArray data("testData"); + mimeData->setData("text/plain", data); + QDrag drag(&window); + drag.setMimeData(mimeData); + drag.exec(); +} + +QCOMPOSITOR_TEST_MAIN(tst_datadevicev1) +#include "tst_datadevicev1.moc" diff --git a/tests/auto/client/fullscreenshellv1/CMakeLists.txt b/tests/auto/client/fullscreenshellv1/CMakeLists.txt new file mode 100644 index 000000000..7bc14c50d --- /dev/null +++ b/tests/auto/client/fullscreenshellv1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from fullscreenshellv1.pro. + +##################################################################### +## tst_client_fullscreenshellv1 Test: +##################################################################### + +qt_internal_add_test(tst_client_fullscreenshellv1 + SOURCES + tst_fullscreenshellv1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/fullscreenshellv1/tst_fullscreenshellv1.cpp b/tests/auto/client/fullscreenshellv1/tst_fullscreenshellv1.cpp new file mode 100644 index 000000000..ba897d53f --- /dev/null +++ b/tests/auto/client/fullscreenshellv1/tst_fullscreenshellv1.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// Copyright (C) 2018 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <QRasterWindow> + +#include <QtTest/QtTest> + +using namespace MockCompositor; + +class tst_WaylandClientFullScreenShellV1 : public QObject, private DefaultCompositor +{ + Q_OBJECT + +private slots: + void createDestroyWindow(); +}; + +void tst_WaylandClientFullScreenShellV1::createDestroyWindow() +{ + QRasterWindow window; + window.resize(800, 600); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(fullScreenShellV1()->surfaces().size() == 1); + QCOMPOSITOR_VERIFY(surface(0)); + + window.destroy(); + QCOMPOSITOR_TRY_VERIFY(!surface(0)); +} + +int main(int argc, char **argv) +{ + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); + setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + setenv("QT_WAYLAND_SHELL_INTEGRATION", "fullscreen-shell-v1", 1); + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); // window decorations don't make much sense here + + tst_WaylandClientFullScreenShellV1 tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include <tst_fullscreenshellv1.moc> diff --git a/tests/auto/client/inputcontext/CMakeLists.txt b/tests/auto/client/inputcontext/CMakeLists.txt new file mode 100644 index 000000000..66e5ca825 --- /dev/null +++ b/tests/auto/client/inputcontext/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from inputcontext.pro. + +##################################################################### +## tst_inputcontext Test: +##################################################################### + +qt_internal_add_test(tst_inputcontext + SOURCES + tst_inputcontext.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/inputcontext/tst_inputcontext.cpp b/tests/auto/client/inputcontext/tst_inputcontext.cpp new file mode 100644 index 000000000..47a453bb5 --- /dev/null +++ b/tests/auto/client/inputcontext/tst_inputcontext.cpp @@ -0,0 +1,230 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include "textinput.h" +#include "qttextinput.h" + +#include <QtCore/QString> +#include <QtCore/QByteArray> + +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatforminputcontext.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/qpa/qplatforminputcontextfactory_p.h> + +#include <QtTest/QtTest> + +using namespace MockCompositor; + +class tst_inputcontext : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase(); + void selectingInputContext_data(); + void selectingInputContext(); + void selectingTextInputProtocol_data(); + void selectingTextInputProtocol(); + void inputContextReconfigurationWhenTogglingTextInputExtension(); + +private: + QByteArray inputContextName() const; + + template<typename arg_type> + void ensurePresentOnCompositor() + { + exec([&] { + QList<arg_type *> extensions = getAll<arg_type>(); + if (extensions.size() > 1) + QFAIL("Requested type is a singleton, hence there should not be more then one object returned"); + if (extensions.size() == 0) + add<arg_type>(); + }); + } + + template<typename arg_type> + void ensureNotPresentOnCompositor() + { + exec([&] { + QList<arg_type *> extensions = getAll<arg_type>(); + if (extensions.size() > 1) + QFAIL("Requested type is a singleton, hence there should not be more then one object returned"); + if (extensions.size() == 1) + remove(extensions.first()); + }); + } + + QByteArray mComposeModule = QByteArray("QComposeInputContext"); // default input context + QByteArray mIbusModule = QByteArray("QIBusPlatformInputContext"); + QByteArray mTextInputModule = QByteArray("QtWaylandClient::QWaylandInputContext"); + QByteArray mQtTextInputModule = QByteArray("QtWaylandClient::QWaylandInputMethodContext"); +}; + +void tst_inputcontext::initTestCase() +{ + // Verify that plugins are present and valid + QPlatformInputContext *context = QPlatformInputContextFactory::create(QStringLiteral("compose")); + QVERIFY(context && context->isValid()); + + context = QPlatformInputContextFactory::create(QStringLiteral("ibus")); + // The ibus plugin depends on properly configured system services, if plugin is not valid + // verify that wayland qpa plugin properly fallbacks to default input context. + if (!context || !context->isValid()) + mIbusModule = mComposeModule; +} + +QByteArray tst_inputcontext::inputContextName() const +{ + QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration(); + if (platformIntegration->inputContext()) + return platformIntegration->inputContext()->metaObject()->className(); + + return QByteArray(""); +} + +void tst_inputcontext::selectingInputContext_data() +{ + QTest::addColumn<QByteArray>("requestedModule"); + QTest::addColumn<QByteArray>("expectedModule"); + + // Test compositor without Text Input extension + QTest::newRow("ibus") << QByteArray("ibus") << mIbusModule; + QTest::newRow("compose") << QByteArray("compose") << mComposeModule; + QTest::newRow("empty") << QByteArray("") << mComposeModule; + QTest::newRow("null") << QByteArray() << mComposeModule; + QTest::newRow("fake") << QByteArray("fake") << mComposeModule; + + // Test compositor with Text Input extension + QTest::newRow("ibus:text-input") << QByteArray("ibus") << mIbusModule; + QTest::newRow("compose:text-input") << QByteArray("compose") << mComposeModule; + QTest::newRow("empty:text-input") << QByteArray("") << mTextInputModule; + QTest::newRow("null:text-input") << QByteArray() << mTextInputModule; + QTest::newRow("wayland:text-input") << QByteArray("wayland") << mTextInputModule; + QTest::newRow("fake:text-input") << QByteArray("fake") << mComposeModule; +} + +void tst_inputcontext::selectingInputContext() +{ + QFETCH(QByteArray, requestedModule); + QFETCH(QByteArray, expectedModule); + + if (requestedModule.isNull()) + qunsetenv("QT_IM_MODULE"); + else + qputenv("QT_IM_MODULE", requestedModule); + + const bool withTextInputAtCompositorSide = QByteArray(QTest::currentDataTag()).endsWith(":text-input"); + + if (withTextInputAtCompositorSide) + ensurePresentOnCompositor<TextInputManager>(); + else + ensureNotPresentOnCompositor<TextInputManager>(); + + int argc = 0; + QGuiApplication app(argc, nullptr); // loads the platform plugin + + QCOMPARE(inputContextName(), expectedModule); +} + +void tst_inputcontext::selectingTextInputProtocol_data() +{ + QTest::addColumn<bool>("requestQtTextInput"); + QTest::addColumn<bool>("requestTextInput"); + QTest::addColumn<QByteArray>("clientProtocol"); + QTest::addColumn<QByteArray>("expectedModule"); + + QTest::newRow("1-1") << true << true << QByteArray() << mQtTextInputModule; + QTest::newRow("1-2") << true << false << QByteArray() << mQtTextInputModule; + QTest::newRow("1-3") << false << true << QByteArray() << mTextInputModule; + QTest::newRow("1-4") << false << false << QByteArray() << mComposeModule; + + QTest::newRow("2-1") << true << true << QByteArray("zwp_text_input_v2") << mTextInputModule; + QTest::newRow("2-2") << true << false << QByteArray("zwp_text_input_v2") << mComposeModule; + QTest::newRow("2-3") << false << true << QByteArray("zwp_text_input_v2") << mTextInputModule; + QTest::newRow("2-4") << false << false << QByteArray("zwp_text_input_v2") << mComposeModule; + + QTest::newRow("3-1") << true << true << QByteArray("qt_text_input_method_v1") << mQtTextInputModule; + QTest::newRow("3-2") << true << false << QByteArray("qt_text_input_method_v1") << mQtTextInputModule; + QTest::newRow("3-3") << false << true << QByteArray("qt_text_input_method_v1") << mComposeModule; + QTest::newRow("3-4") << false << false << QByteArray("qt_text_input_method_v1") << mComposeModule; + + QTest::newRow("4-1") << true << true << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mQtTextInputModule; + QTest::newRow("4-2") << true << false << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mQtTextInputModule; + QTest::newRow("4-3") << false << true << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mTextInputModule; + QTest::newRow("4-4") << false << false << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mComposeModule; + + QTest::newRow("5-1") << true << true << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mTextInputModule; + QTest::newRow("5-2") << true << false << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mQtTextInputModule; + QTest::newRow("5-3") << false << true << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mTextInputModule; + QTest::newRow("5-4") << false << false << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mComposeModule; +} + +void tst_inputcontext::selectingTextInputProtocol() +{ + QFETCH(bool, requestQtTextInput); + QFETCH(bool, requestTextInput); + QFETCH(QByteArray, clientProtocol); + QFETCH(QByteArray, expectedModule); + + exec([] { + qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); + }); + + qunsetenv("QT_IM_MODULE"); + + if (clientProtocol.isNull()) + qunsetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL"); + else + qputenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL", clientProtocol); + + if (requestTextInput) + ensurePresentOnCompositor<TextInputManager>(); + else + ensureNotPresentOnCompositor<TextInputManager>(); + + if (requestQtTextInput) + ensurePresentOnCompositor<QtTextInputManager>(); + else + ensureNotPresentOnCompositor<QtTextInputManager>(); + + int argc = 0; + QGuiApplication app(argc, nullptr); // loads the platform plugin + + QCOMPARE(inputContextName(), expectedModule); +} + +void tst_inputcontext::inputContextReconfigurationWhenTogglingTextInputExtension() +{ + qunsetenv("QT_IM_MODULE"); + + ensurePresentOnCompositor<TextInputManager>(); + int argc = 0; + QGuiApplication app(argc, nullptr); // loads the platform plugin + QCOMPARE(inputContextName(), mTextInputModule); + + // remove text input extension after the platform plugin has been loaded + ensureNotPresentOnCompositor<TextInputManager>(); + // QTRY_* because we need to spin the event loop for wayland QPA plugin + // to handle registry_global_remove() + QTRY_COMPARE(inputContextName(), mComposeModule); + + // add text input extension after the platform plugin has been loaded + ensurePresentOnCompositor<TextInputManager>(); + // QTRY_* because we need to spin the event loop for wayland QPA plugin + // to handle registry_global() + QTRY_COMPARE(inputContextName(), mTextInputModule); +} + +int main(int argc, char *argv[]) +{ + QTemporaryDir tmpRuntimeDir; + qputenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit()); + qputenv("QT_QPA_PLATFORM", "wayland"); + + tst_inputcontext tc; + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_inputcontext.moc" diff --git a/tests/auto/client/iviapplication/CMakeLists.txt b/tests/auto/client/iviapplication/CMakeLists.txt new file mode 100644 index 000000000..a138d3429 --- /dev/null +++ b/tests/auto/client/iviapplication/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from iviapplication.pro. + +##################################################################### +## tst_client_iviapplication Test: +##################################################################### + +qt_internal_add_test(tst_client_iviapplication + SOURCES + tst_iviapplication.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/iviapplication/iviapplication.pro b/tests/auto/client/iviapplication/iviapplication.pro deleted file mode 100644 index 326921373..000000000 --- a/tests/auto/client/iviapplication/iviapplication.pro +++ /dev/null @@ -1,5 +0,0 @@ -include (../shared/shared.pri) - -TARGET = tst_client_iviapplication -SOURCES += tst_iviapplication.cpp - diff --git a/tests/auto/client/iviapplication/tst_iviapplication.cpp b/tests/auto/client/iviapplication/tst_iviapplication.cpp index 59ff6f555..082149f36 100644 --- a/tests/auto/client/iviapplication/tst_iviapplication.cpp +++ b/tests/auto/client/iviapplication/tst_iviapplication.cpp @@ -1,141 +1,83 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockcompositor.h" -#include <QWindow> +#include <QRasterWindow> #include <QtTest/QtTest> -static const QSize screenSize(1600, 1200); +using namespace MockCompositor; -class TestWindow : public QWindow -{ -public: - TestWindow() - { - setSurfaceType(QSurface::RasterSurface); - setGeometry(0, 0, 32, 32); - create(); - } -}; - -class tst_WaylandClientIviApplication : public QObject +class tst_WaylandClientIviApplication : public QObject, private DefaultCompositor { Q_OBJECT -public: - tst_WaylandClientIviApplication(MockCompositor *c) - : m_compositor(c) - { - QSocketNotifier *notifier = new QSocketNotifier(m_compositor->waylandFileDescriptor(), QSocketNotifier::Read, this); - connect(notifier, &QSocketNotifier::activated, this, &tst_WaylandClientIviApplication::processWaylandEvents); - // connect to the event dispatcher to make sure to flush out the outgoing message queue - connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &tst_WaylandClientIviApplication::processWaylandEvents); - connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &tst_WaylandClientIviApplication::processWaylandEvents); - } - -public slots: - void processWaylandEvents() - { - m_compositor->processWaylandEvents(); - } - - void cleanup() - { - // make sure the surfaces from the last test are properly cleaned up - // and don't show up as false positives in the next test - QTRY_VERIFY(!m_compositor->surface()); - QTRY_VERIFY(!m_compositor->iviSurface()); - } private slots: void createDestroyWindow(); void configure(); void uniqueIviIds(); - -private: - MockCompositor *m_compositor = nullptr; }; void tst_WaylandClientIviApplication::createDestroyWindow() { - TestWindow window; + QRasterWindow window; + window.resize(32, 32); window.show(); - QTRY_VERIFY(m_compositor->surface()); - QTRY_VERIFY(m_compositor->iviSurface()); + QCOMPOSITOR_TRY_VERIFY(surface()); + QCOMPOSITOR_TRY_VERIFY(iviSurface()); window.destroy(); - QTRY_VERIFY(!m_compositor->surface()); - QTRY_VERIFY(!m_compositor->iviSurface()); + QCOMPOSITOR_TRY_VERIFY(!surface()); + QCOMPOSITOR_TRY_VERIFY(!iviSurface()); } void tst_WaylandClientIviApplication::configure() { - TestWindow window; + QRasterWindow window; + window.resize(32, 32); window.show(); - QSharedPointer<MockIviSurface> iviSurface; - QTRY_VERIFY(iviSurface = m_compositor->iviSurface()); + QCOMPOSITOR_TRY_VERIFY(iviSurface()); // Unconfigured ivi surfaces decide their own size QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), QSize(32, 32))); - m_compositor->sendIviSurfaceConfigure(iviSurface, {123, 456}); + exec([&] { + iviSurface()->send_configure(123, 456); + }); QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), QSize(123, 456))); + window.destroy(); + QCOMPOSITOR_TRY_VERIFY(!iviSurface()); } void tst_WaylandClientIviApplication::uniqueIviIds() { - TestWindow windowA, windowB; + QRasterWindow windowA, windowB; + windowA.resize(32, 32); windowA.show(); + windowB.resize(32, 32); windowB.show(); - QSharedPointer<MockIviSurface> iviSurface0, iviSurface1; - QTRY_VERIFY(iviSurface0 = m_compositor->iviSurface(0)); - QTRY_VERIFY(iviSurface1 = m_compositor->iviSurface(1)); - QTRY_VERIFY(iviSurface0->iviId != iviSurface1->iviId); + QCOMPOSITOR_TRY_VERIFY(iviSurface(0)); + QCOMPOSITOR_TRY_VERIFY(iviSurface(1)); + exec([&] { + QVERIFY(iviSurface(0)->m_iviId != iviSurface(1)->m_iviId); + }); } int main(int argc, char **argv) { - setenv("XDG_RUNTIME_DIR", ".", 1); + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin setenv("QT_WAYLAND_SHELL_INTEGRATION", "ivi-shell", 1); setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); // window decorations don't make much sense on ivi-application - MockCompositor compositor; - compositor.setOutputMode(screenSize); - + tst_WaylandClientIviApplication tc; QGuiApplication app(argc, argv); - compositor.applicationInitialized(); - - tst_WaylandClientIviApplication tc(&compositor); + QTEST_SET_MAIN_SOURCE_PATH return QTest::qExec(&tc, argc, argv); } diff --git a/tests/auto/client/multithreaded/CMakeLists.txt b/tests/auto/client/multithreaded/CMakeLists.txt new file mode 100644 index 000000000..62d935905 --- /dev/null +++ b/tests/auto/client/multithreaded/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from multithreaded.pro. + +##################################################################### +## tst_multithreaded Test: +##################################################################### + +qt_internal_add_test(tst_multithreaded + SOURCES + tst_multithreaded.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/multithreaded/tst_multithreaded.cpp b/tests/auto/client/multithreaded/tst_multithreaded.cpp new file mode 100644 index 000000000..a1a7d367e --- /dev/null +++ b/tests/auto/client/multithreaded/tst_multithreaded.cpp @@ -0,0 +1,140 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2020 UBports Foundataion. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <unistd.h> +#include <poll.h> + +#include <wayland-client-core.h> + +#include <QtGui/QScreen> +#include <QAbstractEventDispatcher> +#include <qpa/qplatformnativeinterface.h> + +#include "mockcompositor.h" + +using namespace MockCompositor; + +/* + * This class simulate a thread from another library which use poll() to wait + * for data from Wayland compositor. + */ + +class ExternalWaylandReaderThread : public QThread +{ +public: + ExternalWaylandReaderThread(struct wl_display *disp) + : QThread() + , m_disp(disp) + { + setObjectName(QStringLiteral("ExternalWaylandReader")); + } + + ~ExternalWaylandReaderThread() + { + if (m_pipefd[1] != -1 && write(m_pipefd[1], "q", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + wait(); + } + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (::pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + struct wl_event_queue *a_queue = wl_display_create_queue(m_disp); + struct pollfd fds[2] = { { wl_display_get_fd(m_disp), POLLIN, 0 }, + { m_pipefd[0], POLLIN, 0 } }; + + while (true) { + // No wl_proxy is assigned to this queue, thus guaranteed to be always empty. + Q_ASSERT(wl_display_prepare_read_queue(m_disp, a_queue) == 0); + wl_display_flush(m_disp); + + // Wakeup every 10 seconds so that if Qt blocks in _read_events(), + // it won't last forever. + poll(fds, /* nfds */ 2, 10000); + + if (fds[0].revents & POLLIN) { + wl_display_read_events(m_disp); + } else { + wl_display_cancel_read(m_disp); + } + + if (fds[1].revents & POLLIN) { + char pipeIn; + read(m_pipefd[0], &pipeIn, 1); + if (pipeIn == 'q') + break; + } + } + + wl_event_queue_destroy(a_queue); + } + +private: + struct wl_display *m_disp; + int m_pipefd[2] = { -1, -1 }; +}; + +class tst_multithreaded : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase() + { + m_config.autoConfigure = true; + m_config.autoEnter = false; + } + void init() + { + // a test case is given new simulated thread. + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + struct wl_display *wl_dpy = + (struct wl_display *)native->nativeResourceForWindow("display", NULL); + + m_extThread.reset(new ExternalWaylandReaderThread(wl_dpy)); + m_extThread->start(); + } + void cleanup() + { + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + + void mainThreadIsNotBlocked(); + +public: + QScopedPointer<ExternalWaylandReaderThread> m_extThread; +}; + +void tst_multithreaded::mainThreadIsNotBlocked() +{ + QElapsedTimer timer; + timer.start(); + + QTest::qWait(100); + QVERIFY(timer.elapsed() < 200); +} + +QCOMPOSITOR_TEST_MAIN(tst_multithreaded) +#include "tst_multithreaded.moc" diff --git a/tests/auto/client/nooutput/CMakeLists.txt b/tests/auto/client/nooutput/CMakeLists.txt new file mode 100644 index 000000000..eeee57909 --- /dev/null +++ b/tests/auto/client/nooutput/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from nooutput.pro. + +##################################################################### +## tst_nooutput Test: +##################################################################### + +qt_internal_add_test(tst_nooutput + SOURCES + tst_nooutput.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/nooutput/tst_nooutput.cpp b/tests/auto/client/nooutput/tst_nooutput.cpp new file mode 100644 index 000000000..1d8a838f3 --- /dev/null +++ b/tests/auto/client/nooutput/tst_nooutput.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QScreen> +#include <QtGui/QRasterWindow> + +using namespace MockCompositor; + +class NoOutputCompositor : public DefaultCompositor { +public: + NoOutputCompositor() + { + exec([this] { removeAll<Output>(); }); + m_config.autoConfigure = false; + } +}; + +class tst_nooutput : public QObject, private NoOutputCompositor +{ + Q_OBJECT +private slots: + void cleanup() + { + // There should be no wl_outputs in this test + QCOMPOSITOR_COMPARE(getAll<Output>().size(), 0); + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + void noScreens(); +}; + +void tst_nooutput::noScreens() +{ + QRasterWindow window; + window.resize(16, 16); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QTRY_VERIFY(window.isVisible()); + // The window should not be exposed before the first xdg_surface configure event + QTRY_VERIFY(!window.isExposed()); + + exec([&] { + xdgToplevel()->sendConfigure({0, 0}, {}); // Let the window decide the size + xdgSurface()->sendConfigure(nextSerial()); + }); + + QTRY_VERIFY(window.isExposed()); +} + +QCOMPOSITOR_TEST_MAIN(tst_nooutput) +#include "tst_nooutput.moc" diff --git a/tests/auto/client/output/CMakeLists.txt b/tests/auto/client/output/CMakeLists.txt new file mode 100644 index 000000000..a9c5cea3c --- /dev/null +++ b/tests/auto/client/output/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from output.pro. + +##################################################################### +## tst_output Test: +##################################################################### + +qt_internal_add_test(tst_output + SOURCES + tst_output.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/output/tst_output.cpp b/tests/auto/client/output/tst_output.cpp new file mode 100644 index 000000000..2129e167b --- /dev/null +++ b/tests/auto/client/output/tst_output.cpp @@ -0,0 +1,249 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QScreen> +#include <QtGui/QRasterWindow> + +using namespace MockCompositor; + +class tst_output : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase() + { + m_config.autoConfigure = true; + m_config.autoEnter = false; + } + void cleanup() + { + QCOMPOSITOR_COMPARE(getAll<Output>().size(), 1); // Only the default output should be left + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + void primaryScreen(); + void secondaryHiDpiScreen(); + void addScreenWithGeometryChange(); + void windowScreens(); + void removePrimaryScreen(); + void screenOrder(); + void removeAllScreens(); +}; + +void tst_output::primaryScreen() +{ + // Verify that the client has bound to the output global + QCOMPOSITOR_TRY_COMPARE(output()->resourceMap().size(), 1); + QTRY_VERIFY(QGuiApplication::primaryScreen()); + QScreen *screen = QGuiApplication::primaryScreen(); + QCOMPARE(screen->manufacturer(), "Make"); + QCOMPARE(screen->model(), "Model"); + QCOMPARE(screen->size(), QSize(1920, 1080)); + QCOMPARE(screen->refreshRate(), 60); + QCOMPARE(qRound(screen->physicalDotsPerInch()), 96 / screen->devicePixelRatio()); + QCOMPARE(screen->devicePixelRatio(), 1); + QCOMPARE(screen->logicalDotsPerInch(), 96); +} + +void tst_output::secondaryHiDpiScreen() +{ + exec([&] { + OutputData d; + d.position = {1920, 0}; // in global compositor space (not pixels) + d.mode.resolution = {800, 640}; + d.physicalSize = d.mode.physicalSizeForDpi(200); + d.scale = 2; + add<Output>(d); + }); + + // Verify that the client has bound to the output global + QCOMPOSITOR_TRY_VERIFY(output(1) && output(1)->resourceMap().size() == 1); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QScreen *screen = QGuiApplication::screens()[1]; + QCOMPARE(screen->refreshRate(), 60); + QCOMPARE(screen->devicePixelRatio(), 2); + QCOMPARE(screen->logicalDotsPerInch(), 96); + + // Dots currently means device pixels, not actual pixels (see QTBUG-62649) + QCOMPARE(qRound(screen->physicalDotsPerInch() * screen->devicePixelRatio()), 200); + + // Size is in logical pixel coordinates + QCOMPARE(screen->size(), QSize(800, 640) / 2); + QCOMPARE(screen->geometry(), QRect(QPoint(1920, 0), QSize(400, 320))); + QCOMPARE(screen->virtualGeometry(), QRect(QPoint(0, 0), QSize(1920 + 800 / 2, 1080))); + + exec([&] { remove(output(1)); }); +} + +// QTBUG-62044 +void tst_output::addScreenWithGeometryChange() +{ + const QPoint initialPosition = exec([&] { return output(0)->m_data.position; }); + + exec([&] { + auto *oldOutput = output(0); + auto *newOutput = add<Output>(); + newOutput->m_data.mode.resolution = {1280, 720}; + // Move the primary output to the right + QPoint newPosition(newOutput->m_data.mode.resolution.width(), 0); + Q_ASSERT(newPosition != initialPosition); + oldOutput->m_data.position = newPosition; + oldOutput->sendGeometry(); + oldOutput->sendDone(); + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), QRect(QPoint(1280, 0), QSize(1920, 1080))); + + // Remove the extra output and move the old one back + exec([&] { + remove(output(1)); + output()->m_data.position = initialPosition; + output()->sendGeometry(); + output()->sendDone(); + }); + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), QRect(QPoint(0, 0), QSize(1920, 1080))); +} + +void tst_output::windowScreens() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + exec([&] { add<Output>(); }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QScreen *secondaryScreen = QGuiApplication::screens().at(1); + QVERIFY(secondaryScreen); + + window.setScreen(secondaryScreen); + QCOMPARE(window.screen(), secondaryScreen); + + exec([&] { + xdgToplevel()->surface()->sendEnter(output(0)); + xdgToplevel()->surface()->sendEnter(output(1)); + }); + + QTRY_COMPARE(window.screen(), primaryScreen); + + exec([&] { + xdgToplevel()->surface()->sendLeave(output(0)); + }); + QTRY_COMPARE(window.screen(), secondaryScreen); + + exec([&] { + remove(output(1)); + }); + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QCOMPARE(window.screen(), primaryScreen); +} + +void tst_output::removePrimaryScreen() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + // Add a clone of the primary output + exec([&] { add<Output>(output()->m_data); }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(QGuiApplication::primaryScreen()->virtualSiblings().size(), 2); + QScreen *secondaryScreen = QGuiApplication::screens().at(1); + QVERIFY(secondaryScreen); + + exec([&] { remove(output()); }); + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, 1); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, 0); + pointer()->sendFrame(client()); + }); + + // Wait to make sure mouse events dont't cause a crash now that the screen has changed + xdgPingAndWaitForPong(); +} + +// QTBUG-72828 +void tst_output::screenOrder() +{ + exec([&] { + add<Output>()->m_data.model = "Screen 1"; + add<Output>()->m_data.model = "Screen 2"; + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 3); + const auto screens = QGuiApplication::screens(); + + QCOMPARE(screens[1]->model(), "Screen 1"); + QCOMPARE(screens[2]->model(), "Screen 2"); + + exec([&] { + remove(output(2)); + remove(output(1)); + }); +} + +// This is different from tst_nooutput::noScreens because here we have a screen at platform +// integration initialization, which we then remove. +void tst_output::removeAllScreens() +{ + QRasterWindow window1; + window1.resize(400, 320); + window1.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface(0) && xdgSurface(0)->m_committedConfigureSerial); + + const QString wlOutputPrimaryScreenModel = QGuiApplication::primaryScreen()->model(); + + // Get screen info so we can restore it after + auto screenInfo = exec([&] { return output()->m_data; }); + exec([&] { remove(output()); }); + + // Make sure the wl_output is actually removed before we continue + QTRY_VERIFY(!QGuiApplication::primaryScreen() || QGuiApplication::primaryScreen()->model() != wlOutputPrimaryScreenModel); + + // Adding a window while there are no screens should also work + QRasterWindow window2; + window2.resize(400, 320); + window2.show(); + + exec([&] { add<Output>(screenInfo); }); + + // Things should be back to normal + QTRY_VERIFY(QGuiApplication::primaryScreen()); + QTRY_COMPARE(QGuiApplication::primaryScreen()->model(), wlOutputPrimaryScreenModel); + + // Test that we don't leave any fake screens around after we get a wl_output back. + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + + // Qt may choose to recreate/hide windows in response to changing screens, so give the client + // some time to potentially mess up before we verify that the windows are visible. + xdgPingAndWaitForPong(); + + // Windows should be visible after we've reconnected the screen + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(0) && xdgToplevel(0)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1) && xdgToplevel(1)->m_xdgSurface->m_committedConfigureSerial); + +} + +QCOMPOSITOR_TEST_MAIN(tst_output) +#include "tst_output.moc" diff --git a/tests/auto/client/primaryselectionv1/CMakeLists.txt b/tests/auto/client/primaryselectionv1/CMakeLists.txt new file mode 100644 index 000000000..0235ae33e --- /dev/null +++ b/tests/auto/client/primaryselectionv1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from primaryselectionv1.pro. + +##################################################################### +## tst_primaryselectionv1 Test: +##################################################################### + +qt_internal_add_test(tst_primaryselectionv1 + SOURCES + tst_primaryselectionv1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp new file mode 100644 index 000000000..53a048a35 --- /dev/null +++ b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp @@ -0,0 +1,476 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <qwayland-server-wp-primary-selection-unstable-v1.h> + +#include <QtGui/QRasterWindow> +#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; + QList<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); + } +}; + +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) + {} + ~PrimarySelectionDeviceManagerV1() override + { + qDeleteAll(m_devices); + } + bool isClean() override + { + for (auto *device : std::as_const(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; + QList<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 destroysSelectionOnLeave(); + void copy(); +}; + +void tst_primaryselectionv1::initTestCase() +{ + QCOMPOSITOR_TRY_VERIFY(pointer()); + 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, offer, [](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(); + }, Qt::DirectConnection); + device->sendSelection(offer); + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 0); + pointer()->sendFrame(client()); + }); + 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, offer, [](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(); + }, Qt::DirectConnection); + device->sendSelection(offer); + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 0); + pointer()->sendFrame(client()); + }); + 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::destroysSelectionOnLeave() +{ + QRasterWindow 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 *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); + primarySelectionDevice()->sendSelection(offer); + }); + + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Selection)); + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Selection)->hasText()); + + QSignalSpy selectionChangedSpy(QGuiApplication::clipboard(), &QClipboard::selectionChanged); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendLeave(surface); + }); + + QTRY_COMPARE(selectionChangedSpy.size(), 1); + QVERIFY(!QGuiApplication::clipboard()->mimeData(QClipboard::Selection)->hasText()); +} + +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); + QList<uint> mouseSerials; + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendFrame(client()); + mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 0); + pointer()->sendFrame(client()); + }); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->m_selectionSource); + QCOMPOSITOR_TRY_VERIFY(mouseSerials.contains(primarySelectionDevice()->m_serial)); + QVERIFY(QGuiApplication::clipboard()->ownsSelection()); + 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); + } + }); + }, Qt::DirectConnection); + }); + + 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/reconnect/CMakeLists.txt b/tests/auto/client/reconnect/CMakeLists.txt new file mode 100644 index 000000000..07d4c7143 --- /dev/null +++ b/tests/auto/client/reconnect/CMakeLists.txt @@ -0,0 +1,11 @@ +##################################################################### +## tst_wl_reconnect Test: +##################################################################### + +qt_internal_add_test(tst_wl_reconnect + SOURCES + wl-socket.c + tst_reconnect.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/reconnect/tst_reconnect.cpp b/tests/auto/client/reconnect/tst_reconnect.cpp new file mode 100644 index 000000000..ff806af6a --- /dev/null +++ b/tests/auto/client/reconnect/tst_reconnect.cpp @@ -0,0 +1,253 @@ +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <QBackingStore> +#include <QPainter> +#include <QScreen> +#include <QWindow> +#include <QMimeData> +#include <QPixmap> +#include <QDrag> +#include <QWindow> +#if QT_CONFIG(opengl) +#include <QOpenGLWindow> +#endif +#include <QRasterWindow> + +#include <QtTest/QtTest> +#include <QtWaylandClient/private/qwaylandintegration_p.h> +#include <QtGui/private/qguiapplication_p.h> + +#include "wl-socket.h" + +using namespace MockCompositor; + +class TestWindow : public QRasterWindow +{ +public: + TestWindow() + { + } + + void focusInEvent(QFocusEvent *) override + { + ++focusInEventCount; + } + + void focusOutEvent(QFocusEvent *) override + { + ++focusOutEventCount; + } + + void keyPressEvent(QKeyEvent *event) override + { + ++keyPressEventCount; + keyCode = event->nativeScanCode(); + } + + void keyReleaseEvent(QKeyEvent *event) override + { + ++keyReleaseEventCount; + keyCode = event->nativeScanCode(); + } + + void mousePressEvent(QMouseEvent *event) override + { + ++mousePressEventCount; + mousePressPos = event->position().toPoint(); + } + + void mouseReleaseEvent(QMouseEvent *) override + { + ++mouseReleaseEventCount; + } + + void touchEvent(QTouchEvent *event) override + { + Q_UNUSED(event); + ++touchEventCount; + } + + QPoint frameOffset() const { return QPoint(frameMargins().left(), frameMargins().top()); } + + int focusInEventCount = 0; + int focusOutEventCount = 0; + int keyPressEventCount = 0; + int keyReleaseEventCount = 0; + int mousePressEventCount = 0; + int mouseReleaseEventCount = 0; + int touchEventCount = 0; + + uint keyCode = 0; + QPoint mousePressPos; +}; + +class tst_WaylandReconnect : public QObject +{ + Q_OBJECT +public: + tst_WaylandReconnect(); + void triggerReconnect(); + + template<typename function_type, typename... arg_types> + auto exec(function_type func, arg_types&&... args) -> decltype(func()) + { + return m_comp->exec(func, std::forward<arg_types>(args)...); + } + +private Q_SLOTS: +//core + void cleanup() { QTRY_VERIFY2(m_comp->isClean(), qPrintable(m_comp->dirtyMessage())); } + void basicWindow(); + void multipleScreens(); + +//input + void keyFocus(); + +private: + void configureWindow(); + QScopedPointer<DefaultCompositor> m_comp; + wl_socket *m_socket; +}; + +tst_WaylandReconnect::tst_WaylandReconnect() +{ + m_socket = wl_socket_create(); + QVERIFY(m_socket); + const int socketFd = wl_socket_get_fd(m_socket); + const QByteArray socketName = wl_socket_get_display_name(m_socket); + qputenv("WAYLAND_DISPLAY", socketName); + + m_comp.reset(new DefaultCompositor(CoreCompositor::Default, dup(socketFd))); +} + +void tst_WaylandReconnect::triggerReconnect() +{ + const int socketFd = wl_socket_get_fd(m_socket); + m_comp.reset(new DefaultCompositor(CoreCompositor::Default, dup(socketFd))); + QTest::qWait(50); //we need to spin the main loop to actually reconnect +} + +void tst_WaylandReconnect::basicWindow() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); + + triggerReconnect(); + + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); +} + +void tst_WaylandReconnect::multipleScreens() +{ + + exec([this] { m_comp->add<Output>(); }); + QRasterWindow window1; + window1.resize(64, 48); + window1.show(); + QRasterWindow window2; + window2.resize(64, 48); + window2.show(); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(0)); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(1)); + + // ensure they are on different outputs + exec([this] { + m_comp->surface(0)->sendEnter(m_comp->output(0)); + m_comp->surface(1)->sendEnter(m_comp->output(1)); + }); + QTRY_VERIFY(window1.screen() != window2.screen()); + + auto originalScreens = QGuiApplication::screens(); + + QSignalSpy screenRemovedSpy(qGuiApp, &QGuiApplication::screenRemoved); + QVERIFY(screenRemovedSpy.isValid()); + + triggerReconnect(); + + // All screens plus temporary placeholder screen removed + QCOMPARE(screenRemovedSpy.count(), originalScreens.count() + 1); + for (const auto &screen : std::as_const(screenRemovedSpy)) { + originalScreens.removeOne(screen[0].value<QScreen *>()); + } + QVERIFY(originalScreens.isEmpty()); + + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(0)); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(1)); + QVERIFY(window1.screen()); + QVERIFY(window2.screen()); + QVERIFY(QGuiApplication::screens().contains(window1.screen())); + QVERIFY(QGuiApplication::screens().contains(window2.screen())); +} + +void tst_WaylandReconnect::keyFocus() +{ + TestWindow window; + window.resize(64, 48); + window.show(); + + configureWindow(); + QTRY_VERIFY(window.isExposed()); + exec([&] { + m_comp->keyboard()->sendEnter(m_comp->surface()); + }); + QTRY_COMPARE(window.focusInEventCount, 1); + + uint keyCode = 80; + QCOMPARE(window.keyPressEventCount, 0); + exec([&] { + m_comp->keyboard()->sendKey(m_comp->client(), keyCode - 8, Keyboard::key_state_pressed); + }); + QTRY_COMPARE(window.keyPressEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), &window); + + triggerReconnect(); + configureWindow(); + + // on reconnect our knowledge of focus is reset to a clean slate + QCOMPARE(QGuiApplication::focusWindow(), nullptr); + QTRY_COMPARE(window.focusOutEventCount, 1); + + // fake the user explicitly focussing this window afterwards + exec([&] { + m_comp->keyboard()->sendEnter(m_comp->surface()); + }); + exec([&] { + m_comp->keyboard()->sendKey(m_comp->client(), keyCode - 8, Keyboard::key_state_pressed); + }); + QTRY_COMPARE(window.focusInEventCount, 2); + QTRY_COMPARE(window.keyPressEventCount, 2); +} + + +void tst_WaylandReconnect::configureWindow() +{ + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); + m_comp->exec([&] { + m_comp->xdgToplevel()->sendConfigure({0, 0}, {}); + const uint serial = m_comp->nextSerial(); // Let the window decide the size + m_comp->xdgSurface()->sendConfigure(serial); + }); +} + +int main(int argc, char **argv) +{ + // Note when debugging that a failing reconnect will exit this + // test rather than fail. Making sure it finishes is important! + + QTemporaryDir tmpRuntimeDir; + setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + setenv("QT_WAYLAND_RECONNECT", "1", 1); + setenv("XDG_CURRENT_DESKTOP", "qtwaylandtests", 1); + + tst_WaylandReconnect tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_reconnect.moc" diff --git a/tests/auto/client/reconnect/wl-socket.c b/tests/auto/client/reconnect/wl-socket.c new file mode 100644 index 000000000..3d4491622 --- /dev/null +++ b/tests/auto/client/reconnect/wl-socket.c @@ -0,0 +1,166 @@ +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#define _DEFAULT_SOURCE +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> + +/* This is the size of the char array in struct sock_addr_un. + * No Wayland socket can be created with a path longer than this, + * including the null terminator. + */ +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIXLEN 5 + +struct wl_socket { + int fd; + int fd_lock; + struct sockaddr_un addr; + char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN]; + char display_name[16]; +}; + +static struct wl_socket *wl_socket_alloc(void) +{ + struct wl_socket *s; + + s = malloc(sizeof *s); + if (!s) + return NULL; + + s->fd = -1; + s->fd_lock = -1; + + return s; +} + +static int wl_socket_lock(struct wl_socket *socket) +{ + struct stat socket_stat; + + snprintf(socket->lock_addr, sizeof socket->lock_addr, "%s%s", socket->addr.sun_path, LOCK_SUFFIX); + + // differening from kwin, we're back to setting CLOEXEC as we're all in process + socket->fd_lock = open(socket->lock_addr, O_CREAT | O_RDWR | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + if (socket->fd_lock < 0) { + printf("unable to open lockfile %s check permissions\n", socket->lock_addr); + goto err; + } + + if (flock(socket->fd_lock, LOCK_EX | LOCK_NB) < 0) { + printf("unable to lock lockfile %s, maybe another compositor is running\n", socket->lock_addr); + goto err_fd; + } + + if (lstat(socket->addr.sun_path, &socket_stat) < 0) { + if (errno != ENOENT) { + printf("did not manage to stat file %s\n", socket->addr.sun_path); + goto err_fd; + } + } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) { + unlink(socket->addr.sun_path); + } + + return 0; +err_fd: + close(socket->fd_lock); + socket->fd_lock = -1; +err: + *socket->lock_addr = 0; + /* we did not set this value here, but without lock the + * socket won't be created anyway. This prevents the + * wl_socket_destroy from unlinking already existing socket + * created by other compositor */ + *socket->addr.sun_path = 0; + + return -1; +} + +void wl_socket_destroy(struct wl_socket *s) +{ + if (s->addr.sun_path[0]) + unlink(s->addr.sun_path); + if (s->fd >= 0) + close(s->fd); + if (s->lock_addr[0]) + unlink(s->lock_addr); + if (s->fd_lock >= 0) + close(s->fd_lock); + + free(s); +} + +const char *wl_socket_get_display_name(struct wl_socket *s) +{ + return s->display_name; +} + +int wl_socket_get_fd(struct wl_socket *s) +{ + return s->fd; +} + +struct wl_socket *wl_socket_create() +{ + struct wl_socket *s; + int displayno = 0; + int name_size; + + /* A reasonable number of maximum default sockets. If + * you need more than this, use the explicit add_socket API. */ + const int MAX_DISPLAYNO = 32; + const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (!runtime_dir) { + printf("XDG_RUNTIME_DIR not set"); + return NULL; + } + + s = wl_socket_alloc(); + if (s == NULL) + return NULL; + + do { + snprintf(s->display_name, sizeof s->display_name, "wayland-%d", displayno); + s->addr.sun_family = AF_LOCAL; + name_size = snprintf(s->addr.sun_path, sizeof s->addr.sun_path, "%s/%s", runtime_dir, s->display_name) + 1; + assert(name_size > 0); + + if (name_size > (int)sizeof s->addr.sun_path) { + goto fail; + } + + if (wl_socket_lock(s) < 0) + continue; + + s->fd = socket(PF_LOCAL, SOCK_STREAM, 0); + + int size = SUN_LEN(&s->addr); + int ret = bind(s->fd, (struct sockaddr*)&s->addr, size); + if (ret < 0) { + goto fail; + } + ret = listen(s->fd, 128); + if (ret < 0) { + goto fail; + } + return s; + } while (displayno++ < MAX_DISPLAYNO); + +fail: + wl_socket_destroy(s); + return NULL; +} diff --git a/tests/auto/client/reconnect/wl-socket.h b/tests/auto/client/reconnect/wl-socket.h new file mode 100644 index 000000000..c2e68120f --- /dev/null +++ b/tests/auto/client/reconnect/wl-socket.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Allocate and create a socket + * It is bound and accepted + */ +struct wl_socket *wl_socket_create(); + +/** + * Returns the file descriptor for the socket + */ +int wl_socket_get_fd(struct wl_socket *); + +/** + * Returns the name of the socket, i.e "wayland-0" + */ +char *wl_socket_get_display_name(struct wl_socket *); + +/** + * Cleanup resources and close the FD + */ +void wl_socket_destroy(struct wl_socket *socket); + +#ifdef __cplusplus +} +#endif diff --git a/tests/auto/client/scaling/CMakeLists.txt b/tests/auto/client/scaling/CMakeLists.txt new file mode 100644 index 000000000..e58981388 --- /dev/null +++ b/tests/auto/client/scaling/CMakeLists.txt @@ -0,0 +1,10 @@ +##################################################################### +## tst_scaling Test: +##################################################################### + +qt_internal_add_test(tst_scaling + SOURCES + tst_scaling.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/scaling/tst_scaling.cpp b/tests/auto/client/scaling/tst_scaling.cpp new file mode 100644 index 000000000..b4d2995ea --- /dev/null +++ b/tests/auto/client/scaling/tst_scaling.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtWaylandClient/private/wayland-wayland-client-protocol.h> +#include <QtWaylandClient/private/qwaylandwindow_p.h> + +using namespace MockCompositor; + +class tst_scaling : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void scaledWindow(); + void roundingPolicy_data(); + void roundingPolicy(); + +}; + +void tst_scaling::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_scaling::scaledWindow() +{ + QRasterWindow window; + window.resize(100, 100); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + QSignalSpy surfaceCommitSpy(exec([&] { return surface(); }), &Surface::commit); + + const QSize configureSize(100, 100); + + exec([&] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(1.5 * 120); + xdgToplevel()->sendCompleteConfigure(configureSize); + }); + + QTRY_COMPARE(configureSpy.count(), 1); + QCOMPARE(window.devicePixelRatio(), 1.5); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(150, 150)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(100, 100)); + }); + + // resize the window + window.resize(200,200); + QCOMPARE(window.size(), QSize(200,200)); + + QVERIFY(surfaceCommitSpy.wait()); + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(300, 300)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(200, 200)); + }); + + // dynamic scale change + exec([&] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(2.5 * 120); + }); + QTRY_COMPARE(window.devicePixelRatio(), 2.5); + QCOMPARE(window.size(), QSize(200,200)); + + QVERIFY(surfaceCommitSpy.wait()); + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(500, 500)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(200, 200)); + }); +} + +void tst_scaling::roundingPolicy_data() +{ + QTest::addColumn<QSize>("windowSize"); + QTest::addColumn<qreal>("scale"); + QTest::addColumn<QSize>("expectedBufferSize"); + + QTest::newRow("1.125 - round down") << QSize(10, 10) << 1.125 << QSize(11,11); + QTest::newRow("1.25 - round up") << QSize(10, 10) << 1.25 << QSize(13,13); + QTest::newRow("1.5 - don't round") << QSize(10, 10) << 1.5 << QSize(15,15); +} + +void tst_scaling::roundingPolicy() +{ + QFETCH(QSize, windowSize); + QFETCH(qreal, scale); + QFETCH(QSize, expectedBufferSize); + + + QRasterWindow window; + window.resize(windowSize); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy surfaceCommitSpy(exec([&] { return surface(); }), &Surface::commit); + + exec([&] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(scale * 120); + xdgToplevel()->sendCompleteConfigure(); + }); + + QVERIFY(surfaceCommitSpy.wait()); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), expectedBufferSize); + }); +} + + +QCOMPOSITOR_TEST_MAIN(tst_scaling) +#include "tst_scaling.moc" diff --git a/tests/auto/client/seat/CMakeLists.txt b/tests/auto/client/seat/CMakeLists.txt new file mode 100644 index 000000000..0ac9ec49f --- /dev/null +++ b/tests/auto/client/seat/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from seatv5.pro. + +##################################################################### +## tst_seatv5 Test: +##################################################################### + +qt_internal_add_test(tst_seat + SOURCES + tst_seat.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/seat/tst_seat.cpp b/tests/auto/client/seat/tst_seat.cpp new file mode 100644 index 000000000..45ae7251c --- /dev/null +++ b/tests/auto/client/seat/tst_seat.cpp @@ -0,0 +1,685 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/QEventPoint> + +using namespace MockCompositor; + +class SeatCompositor : public DefaultCompositor { +public: + explicit SeatCompositor() + { + exec([this] { + m_config.autoConfigure = true; + + removeAll<Seat>(); + + uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; + int version = 9; + add<Seat>(capabilities, version); + }); + } +}; + +class tst_seat : public QObject, private SeatCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void bindsToSeat(); + + // Pointer tests + void createsPointer(); + void setsCursorOnEnter(); + void usesEnterSerial(); + void simpleAxis_data(); + void simpleAxis(); + void fingerScroll(); + void fingerScrollSlow(); + void continuousScroll(); + void highResolutionScroll(); + + // Touch tests + void createsTouch(); + void singleTap(); + void singleTapFloat(); + void multiTouch(); + void multiTouchUpAndMotionFrame(); + void tapAndMoveInSameFrame(); + void cancelTouch(); +}; + +void tst_seat::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 9); +} + +void tst_seat::createsPointer() +{ + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 9); +} + +void tst_seat::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_seat::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.size(), 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) { + // Shouldn't have deltas in the these phases + QCOMPARE(event->angleDelta(), QPoint(0, 0)); + QCOMPARE(event->pixelDelta(), QPoint(0, 0)); + } + + // We didn't press any buttons + QCOMPARE(event->buttons(), Qt::NoButton); + + 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()) + , source(event->source()) + , inverted(event->inverted()) + { + } + Qt::ScrollPhase phase{}; + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + Qt::MouseEventSource source{}; + bool inverted = false; + }; + QList<Event> m_events; +}; + +void tst_seat::simpleAxis_data() +{ + QTest::addColumn<uint>("axis"); + QTest::addColumn<qreal>("value"); + QTest::addColumn<QPoint>("angleDelta"); + QTest::addColumn<bool>("inverted"); + + // Directions in regular windows/linux terms (no "natural" scrolling) + QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12} << false; + QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12} << false; + QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0} << false; + QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0} << false; + QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120} << false; + + // (natural) scrolling + QTest::newRow("down inverted") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12} << true; + QTest::newRow("up inverted") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12} << true; + QTest::newRow("left inverted") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0} << true; + QTest::newRow("right inverted") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0} << true; + QTest::newRow("up big inverted") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120} << true; +} + +void tst_seat::simpleAxis() +{ + QFETCH(uint, axis); + QFETCH(qreal, value); + QFETCH(QPoint, angleDelta); + QFETCH(bool, inverted); + + 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 + ); + auto direction = inverted ? Pointer::axis_relative_direction_inverted : Pointer::axis_relative_direction_identical; + p->sendAxisRelativeDirection(client(), Pointer::axis(axis), direction); + 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.angleDelta, angleDelta); + + QCOMPARE(e.inverted, inverted); + } + + // Sending axis_stop is not mandatory when axis source != finger +} + +void tst_seat::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); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll +// 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); + QVERIFY(qAbs(e.angleDelta.x()) > qAbs(e.angleDelta.y())); // Horizontal scroll + 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_seat::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_seat::highResolutionScroll() +{ + 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->sendAxisValue120(c, Pointer::axis_vertical_scroll, 30); // quarter of a click + p->sendAxis(c, Pointer::axis_vertical_scroll, 3.75); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + QCOMPARE(e.angleDelta, QPoint(0, -30)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendAxisSource(c, Pointer::axis_source_wheel); + p->sendAxisValue120(c, Pointer::axis_vertical_scroll, 90); // complete the click + p->sendAxis(c, Pointer::axis_vertical_scroll, 11.25); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + QCOMPARE(e.angleDelta, QPoint(0, -90)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } +} + +void tst_seat::continuousScroll() +{ + 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_continuous); + p->sendAxis(c, Pointer::axis_vertical_scroll, 10); + p->sendAxis(c, Pointer::axis_horizontal_scroll, -5); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QCOMPARE(e.pixelDelta, QPoint(5, -10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // touchpads are not wheels + QCOMPARE(e.inverted, false); + + } + // Sending axis_stop is not mandatory when axis source != finger +} + +void tst_seat::createsTouch() +{ + QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().first()->version(), 9); +} + +class TouchWindow : public QRasterWindow { +public: + TouchWindow() + { + resize(64, 64); + show(); + } + void touchEvent(QTouchEvent *event) override + { + QRasterWindow::touchEvent(event); + 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 QTouchEvent *event) + : type(event->type()) + , touchPointStates(event->touchPointStates()) + , touchPoints(event->points()) + { + } + QEvent::Type type{}; + QEventPoint::States touchPointStates{}; + QList<QTouchEvent::TouchPoint> touchPoints; + }; + QList<Event> m_events; +}; + +void tst_seat::singleTap() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32, 32}, 1); + t->sendFrame(c); + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } +} + +void tst_seat::singleTapFloat() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32.75, 32.25}, 1); + t->sendFrame(c); + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top())); + } +} + +void tst_seat::multiTouch() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + + t->sendDown(xdgToplevel()->surface(), {32, 32}, 0); + t->sendDown(xdgToplevel()->surface(), {48, 48}, 1); + t->sendFrame(c); + + // Compositor event order should not change the order of the QTouchEvent::points() + // See QTBUG-77014 + t->sendMotion(c, {49, 48}, 1); + t->sendMotion(c, {33, 32}, 0); + t->sendFrame(c); + + t->sendUp(c, 0); + t->sendFrame(c); + + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 2); + + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints[0].position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints[1].position(), QPointF(48-window.frameMargins().left(), 48-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchUpdate); + QCOMPARE(e.touchPoints.size(), 2); + + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Updated); + QCOMPARE(e.touchPoints[0].position(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top())); + + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Updated); + QCOMPARE(e.touchPoints[1].position(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchUpdate); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released | QEventPoint::State::Stationary); + QCOMPARE(e.touchPoints.size(), 2); + + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + QCOMPARE(e.touchPoints[0].position(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top())); + + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Stationary); + QCOMPARE(e.touchPoints[1].position(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + QCOMPARE(e.touchPoints[0].position(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top())); + } +} + +void tst_seat::multiTouchUpAndMotionFrame() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + + t->sendDown(xdgToplevel()->surface(), {32, 32}, 0); + t->sendDown(xdgToplevel()->surface(), {48, 48}, 1); + t->sendFrame(c); + + // Sending an up event after a frame event, before any motion or down events used to + // unnecessarily trigger a workaround for a bug in an old version of Weston. The workaround + // would prematurely insert a fake frame event splitting the touch event up into two events. + // However, this should only be needed on the up event for the very last touch point. So in + // this test we verify that it doesn't unncecessarily break up the events. + t->sendUp(c, 0); + t->sendMotion(c, {49, 48}, 1); + t->sendFrame(c); + + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Pressed); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchUpdate); + QCOMPARE(e.touchPoints.size(), 2); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Updated); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + } + QVERIFY(window.m_events.empty()); +} + +void tst_seat::tapAndMoveInSameFrame() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + + t->sendDown(xdgToplevel()->surface(), {32, 32}, 0); + t->sendMotion(c, {33, 33}, 0); + t->sendFrame(c); + + // Don't leave touch in a weird state + t->sendUp(c, 0); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Pressed); + // Position isn't that important, we just want to make sure we actually get the pressed event + } + + // Make sure we eventually release + QTRY_VERIFY(!window.m_events.empty()); + QTRY_COMPARE(window.m_events.last().touchPoints.first().state(), QEventPoint::State::Released); +} + +void tst_seat::cancelTouch() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32, 32}, 1); + t->sendFrame(c); + t->sendCancel(c); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchCancel); + QCOMPARE(e.touchPoints.size(), 0); + } +} + +QCOMPOSITOR_TEST_MAIN(tst_seat) +#include "tst_seat.moc" diff --git a/tests/auto/client/seatv4/CMakeLists.txt b/tests/auto/client/seatv4/CMakeLists.txt new file mode 100644 index 000000000..c4e3ecba1 --- /dev/null +++ b/tests/auto/client/seatv4/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from seatv4.pro. + +##################################################################### +## tst_seatv4 Test: +##################################################################### + +qt_internal_add_test(tst_seatv4 + SOURCES + tst_seatv4.cpp + LIBRARIES + SharedClientTest +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(tst_seatv4 CONDITION QT_FEATURE_cursor + LIBRARIES + Qt::GuiPrivate + Wayland::Cursor +) diff --git a/tests/auto/client/seatv4/tst_seatv4.cpp b/tests/auto/client/seatv4/tst_seatv4.cpp new file mode 100644 index 000000000..cc1cf0afd --- /dev/null +++ b/tests/auto/client/seatv4/tst_seatv4.cpp @@ -0,0 +1,584 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <QtGui/QRasterWindow> +#if QT_CONFIG(cursor) +#include <wayland-cursor.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtWaylandClient/private/qwaylanddisplay_p.h> +#include <QtWaylandClient/private/qwaylandintegration_p.h> +#endif + +using namespace MockCompositor; + +// wl_seat version 5 was introduced in wayland 1.10, and although that's pretty old, +// there are still compositors that have yet to update their implementation to support +// the new version (most importantly our own QtWaylandCompositor). +// As long as that's the case, this test makes sure input events still works on version 4. +class SeatV4Compositor : public DefaultCompositor { +public: + explicit SeatV4Compositor() + { + exec([this] { + m_config.autoConfigure = true; + m_config.autoFrameCallback = false; // for cursor animation test + + removeAll<Seat>(); + + uint capabilities = Seat::capability_pointer | Seat::capability_keyboard; + int version = 4; + add<Seat>(capabilities, version); + }); + } +}; + +class tst_seatv4 : public QObject, private SeatV4Compositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup(); + void bindsToSeat(); + void keyboardKeyPress(); +#if QT_CONFIG(cursor) + void createsPointer(); + void setsCursorOnEnter(); + void usesEnterSerial(); + void focusDestruction(); + void mousePress(); + void mousePressFloat(); + void simpleAxis_data(); + void simpleAxis(); + void invalidPointerEvents(); + void scaledCursor(); + void unscaledFallbackCursor(); + void bitmapCursor(); + void hidpiBitmapCursor(); + void hidpiBitmapCursorNonInt(); + void animatedCursor(); +#endif +}; + +void tst_seatv4::init() +{ + // Remove the extra outputs to clean up for the next test + exec([&] { while (auto *o = output(1)) remove(o); }); +} + +void tst_seatv4::cleanup() +{ + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + QCOMPOSITOR_COMPARE(getAll<Output>().size(), 1); // No extra outputs left +} + +void tst_seatv4::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 4); +} + +void tst_seatv4::keyboardKeyPress() +{ + class Window : public QRasterWindow { + public: + void keyPressEvent(QKeyEvent *) override { m_pressed = true; } + bool m_pressed = false; + }; + + Window window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + uint keyCode = 80; // arbitrarily chosen + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); + keyboard()->sendKey(client(), keyCode, Keyboard::key_state_pressed); + keyboard()->sendKey(client(), keyCode, Keyboard::key_state_released); + }); + QTRY_VERIFY(window.m_pressed); +} + +#if QT_CONFIG(cursor) + +void tst_seatv4::createsPointer() +{ + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 4); +} + +void tst_seatv4::setsCursorOnEnter() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {24, 24}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); +} + +void tst_seatv4::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, {32, 32}); + }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + + QTRY_COMPARE(setCursorSpy.size(), 1); + QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial); +} + +void tst_seatv4::focusDestruction() +{ + QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor); + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + // Setting a cursor now is not allowed since there has been no enter event + QCOMPARE(setCursorSpy.size(), 0); + + uint enterSerial = exec([&] { + return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); + }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QTRY_COMPARE(setCursorSpy.size(), 1); + QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial); + + // Destroy the focus + window.close(); + + QRasterWindow window2; + window2.resize(64, 64); + window2.show(); + window2.setCursor(Qt::WaitCursor); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + // Setting a cursor now is not allowed since there has been no enter event + xdgPingAndWaitForPong(); + QCOMPARE(setCursorSpy.size(), 0); +} + +void tst_seatv4::mousePress() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_pressed = true; } + bool m_pressed = false; + }; + + Window window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendButton(client(), BTN_LEFT, 1); + pointer()->sendButton(client(), BTN_LEFT, 0); + }); + QTRY_VERIFY(window.m_pressed); +} + +void tst_seatv4::mousePressFloat() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *e) override { m_position = e->position(); } + QPointF m_position; + }; + + Window window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {32.75, 32.25}); + pointer()->sendButton(client(), BTN_LEFT, 1); + pointer()->sendButton(client(), BTN_LEFT, 0); + }); + QMargins m = window.frameMargins(); + QPointF pressedPosition(32.75 -m.left(), 32.25 - m.top()); + QTRY_COMPARE(window.m_position, pressedPosition); +} + +void tst_seatv4::simpleAxis_data() +{ + QTest::addColumn<uint>("axis"); + QTest::addColumn<qreal>("value"); + QTest::addColumn<QPoint>("angleDelta"); + + // Directions in regular windows/linux terms (no "natural" scrolling) + QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12}; + QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12}; + QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0}; + QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0}; + QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120}; +} + +void tst_seatv4::simpleAxis() +{ + QFETCH(uint, axis); + QFETCH(qreal, value); + QFETCH(QPoint, angleDelta); + + class WheelWindow : QRasterWindow { + public: + explicit WheelWindow() + { + resize(64, 64); + show(); + } + void wheelEvent(QWheelEvent *event) override + { + QRasterWindow::wheelEvent(event); + // Angle delta should always be provided (says docs) + QVERIFY(!event->angleDelta().isNull()); + + // There are now scroll phases on Wayland prior to v5 + QCOMPARE(event->phase(), Qt::NoScrollPhase); + + // Pixel delta should only be set if we know it's a high-res input device (which we don't) + 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); + + // There has been no information about what created the event. + // Documentation says not synthesized is appropriate in such cases + QCOMPARE(event->source(), Qt::MouseEventNotSynthesized); + + m_events.append(Event{event->pixelDelta(), event->angleDelta()}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + }; + QList<Event> m_events; + }; + + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + Surface *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {32, 32}); + wl_client *client = surface->resource()->client(); + // Length of vector in surface-local space. i.e. positive is downwards + pointer()->sendAxis( + client, + Pointer::axis(axis), + value // Length of vector in surface-local space. i.e. positive is downwards + ); + }); + + QTRY_COMPARE(window.m_events.size(), 1); + auto event = window.m_events.takeFirst(); + QCOMPARE(event.angleDelta, angleDelta); +} + +void tst_seatv4::invalidPointerEvents() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + // Purposefully send events without a wl_pointer.enter + p->sendMotion(c, {32, 32}); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); + }); + + // Make sure we get here without crashing + xdgPingAndWaitForPong(); +} + +static bool supportsCursorSize(uint size, wl_shm *shm) +{ + auto *theme = wl_cursor_theme_load(qgetenv("XCURSOR_THEME"), size, shm); + if (!theme) + return false; + + constexpr std::array<const char *, 4> names{"left_ptr", "default", "left_arrow", "top_left_arrow"}; + for (const char *name : names) { + if (auto *cursor = wl_cursor_theme_get_cursor(theme, name)) { + auto *image = cursor->images[0]; + return image->width == image->height && image->width == size; + } + } + return false; +} + +static bool supportsCursorSizes(const QList<uint> &sizes) +{ + auto *waylandIntegration = static_cast<QtWaylandClient::QWaylandIntegration *>(QGuiApplicationPrivate::platformIntegration()); + wl_shm *shm = waylandIntegration->display()->shm()->object(); + return std::all_of(sizes.begin(), sizes.end(), [&](uint size) { + return supportsCursorSize(size, shm); + }); +} + +static uint defaultCursorSize() { + const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE"); + return xCursorSize > 0 ? uint(xCursorSize) : 24; +} + +void tst_seatv4::scaledCursor() +{ + const uint defaultSize = defaultCursorSize(); + if (!supportsCursorSizes({defaultSize, defaultSize * 2})) + QSKIP("Cursor themes with default size and 2x default size not found."); + + // Add a highdpi output + exec([&] { + OutputData d; + d.scale = 2; + d.position = {1920, 0}; + add<Output>(d); + }); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QSize unscaledPixelSize = exec([&] { + return cursorSurface()->m_committed.buffer->size(); + }); + + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll<Output>()[1]); + surface->sendLeave(getAll<Output>()[0]); + }); + + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.buffer->size(), unscaledPixelSize * 2); + + // Remove the extra output to clean up for the next test + exec([&] { remove(output(1)); }); +} + +void tst_seatv4::unscaledFallbackCursor() +{ + const uint defaultSize = defaultCursorSize(); + if (!supportsCursorSizes({defaultSize})) + QSKIP("Default cursor size not supported"); + + const int screens = 4; // with scales 1, 2, 4, 8 + + exec([&] { + for (int i = 1; i < screens; ++i) { + OutputData d; + d.scale = int(qPow(2, i)); + d.position = {1920 * i, 0}; + add<Output>(d); + } + }); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QSize unscaledPixelSize = exec([&] { + return cursorSurface()->m_committed.buffer->size(); + }); + + QCOMPARE(unscaledPixelSize.width(), int(defaultSize)); + QCOMPARE(unscaledPixelSize.height(), int(defaultSize)); + + for (int i = 1; i < screens; ++i) { + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll<Output>()[i]); + surface->sendLeave(getAll<Output>()[i-1]); + }); + + xdgPingAndWaitForPong(); // Give the client a chance to mess up + + // Surface size (buffer size / scale) should stay constant + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.buffer->size() / cursorSurface()->m_committed.bufferScale, unscaledPixelSize); + } + + // Remove the extra outputs to clean up for the next test + exec([&] { while (auto *o = output(1)) remove(o); }); +} + +void tst_seatv4::bitmapCursor() +{ + // Add a highdpi output + exec([&] { + OutputData d; + d.scale = 2; + d.position = {1920, 0}; + add<Output>(d); + }); + + QRasterWindow window; + window.resize(64, 64); + + QPixmap pixmap(24, 24); + pixmap.setDevicePixelRatio(1); + QPoint hotspot(12, 12); // In device pixel coordinates + QCursor cursor(pixmap, hotspot.x(), hotspot.y()); + window.setCursor(cursor); + + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(24, 24)); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll<Output>()[1]); + surface->sendLeave(getAll<Output>()[0]); + }); + + xdgPingAndWaitForPong(); + + // Everything should remain the same, the cursor still has dpr 1 + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(24, 24)); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + // Remove the extra output to clean up for the next test + exec([&] { remove(getAll<Output>()[1]); }); +} + +void tst_seatv4::hidpiBitmapCursor() +{ + // Add a highdpi output + exec([&] { + OutputData d; + d.scale = 2; + d.position = {1920, 0}; + add<Output>(d); + }); + + QRasterWindow window; + window.resize(64, 64); + + QPixmap pixmap(48, 48); + pixmap.setDevicePixelRatio(2); + QPoint hotspot(12, 12); // In device pixel coordinates + QCursor cursor(pixmap, hotspot.x(), hotspot.y()); + window.setCursor(cursor); + + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(48, 48)); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll<Output>()[1]); + surface->sendLeave(getAll<Output>()[0]); + }); + + xdgPingAndWaitForPong(); + + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(48, 48)); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + // Remove the extra output to clean up for the next test + exec([&] { remove(getAll<Output>()[1]); }); +} + +void tst_seatv4::hidpiBitmapCursorNonInt() +{ + QRasterWindow window; + window.resize(64, 64); + + QPixmap pixmap(100, 100); + pixmap.setDevicePixelRatio(2.5); // dpr width is now 100 / 2.5 = 40 + QPoint hotspot(20, 20); // In device pixel coordinates (middle of buffer) + QCursor cursor(pixmap, hotspot.x(), hotspot.y()); + window.setCursor(cursor); + + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(100, 100)); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2); + // Verify that the hotspot was scaled correctly + // Surface size is now 100 / 2 = 50, so the middle should be at 25 in surface coordinates + 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.size(), 1); +} + +#endif // QT_CONFIG(cursor) + +QCOMPOSITOR_TEST_MAIN(tst_seatv4) +#include "tst_seatv4.moc" diff --git a/tests/auto/client/seatv7/CMakeLists.txt b/tests/auto/client/seatv7/CMakeLists.txt new file mode 100644 index 000000000..cf3ade8c7 --- /dev/null +++ b/tests/auto/client/seatv7/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_seatv7 Test: +##################################################################### + +qt_internal_add_test(tst_seatv7 + SOURCES + tst_seatv7.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/seatv7/tst_seatv7.cpp b/tests/auto/client/seatv7/tst_seatv7.cpp new file mode 100644 index 000000000..f82b84b58 --- /dev/null +++ b/tests/auto/client/seatv7/tst_seatv7.cpp @@ -0,0 +1,129 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/QEventPoint> + +using namespace MockCompositor; + +class SeatCompositor : public DefaultCompositor { +public: + explicit SeatCompositor() + { + exec([this] { + m_config.autoConfigure = true; + + removeAll<Seat>(); + + uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; + int version = 7; + add<Seat>(capabilities, version); + }); + } +}; + +class tst_seatv7 : public QObject, private SeatCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void bindsToSeat(); + + // Pointer tests + void wheelDiscreteScroll_data(); + void wheelDiscreteScroll(); +}; + +void tst_seatv7::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 7); +} + +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) { + // Shouldn't have deltas in the these 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); + + 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()) + , source(event->source()) + { + } + Qt::ScrollPhase phase{}; + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + Qt::MouseEventSource source{}; + }; + QList<Event> m_events; +}; + +void tst_seatv7::wheelDiscreteScroll_data() +{ + QTest::addColumn<uint>("source"); + QTest::newRow("wheel") << uint(Pointer::axis_source_wheel); + QTest::newRow("wheel tilt") << uint(Pointer::axis_source_wheel_tilt); +} + +void tst_seatv7::wheelDiscreteScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QFETCH(uint, source); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source(source)); + 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); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + // 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_seatv7) +#include "tst_seatv7.moc" diff --git a/tests/auto/client/shared/CMakeLists.txt b/tests/auto/client/shared/CMakeLists.txt new file mode 100644 index 000000000..1d64f2956 --- /dev/null +++ b/tests/auto/client/shared/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +##Client test shared components: +##################################################################### + +qt_manual_moc(moc_files + mockcompositor.h + coreprotocol.h + corecompositor.h + datadevice.h + fullscreenshellv1.h + fractionalscalev1.h + iviapplication.h + textinput.h + qttextinput.h + viewport.h + xdgdialog.h + xdgoutputv1.h + xdgshell.h +) + +add_library(SharedClientTest + OBJECT + corecompositor.cpp corecompositor.h + coreprotocol.cpp coreprotocol.h + datadevice.cpp datadevice.h + fullscreenshellv1.cpp fullscreenshellv1.h + fractionalscalev1.cpp fractionalscalev1.h + iviapplication.cpp iviapplication.h + mockcompositor.cpp mockcompositor.h + textinput.cpp textinput.h + qttextinput.cpp qttextinput.h + xdgoutputv1.cpp xdgoutputv1.h + xdgshell.cpp xdgshell.h + xdgdialog.cpp xdgdialog.h + viewport.cpp viewport.h + ${moc_files} +) + +qt6_generate_wayland_protocol_server_sources(SharedClientTest + FILES + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/cursor-shape-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/fullscreen-shell-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/ivi-application.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/tablet-unstable-v2.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/text-input-unstable-v2.xml + ${PROJECT_SOURCE_DIR}/src/extensions/qt-text-input-method-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/fractional-scale-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/viewporter.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/wayland.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/xdg-decoration-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/xdg-dialog-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/xdg-output-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/xdg-shell.xml +) + +if(QT_FEATURE_opengl) + set(optional_libraries Qt::OpenGL) +endif() + +target_link_libraries(SharedClientTest + PUBLIC + Qt::Gui + ${optional_libraries} + Qt::WaylandClientPrivate + Wayland::Server + Threads::Threads # special case +) + +target_include_directories(SharedClientTest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/auto/client/shared/corecompositor.cpp b/tests/auto/client/shared/corecompositor.cpp new file mode 100644 index 000000000..d110768ec --- /dev/null +++ b/tests/auto/client/shared/corecompositor.cpp @@ -0,0 +1,123 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "corecompositor.h" +#include <thread> + +namespace MockCompositor { + +CoreCompositor::CoreCompositor(CompositorType t, int socketFd) + : m_type(t) + , m_display(wl_display_create()) + , m_eventLoop(wl_display_get_event_loop(m_display)) + + // Start dispatching + , m_dispatchThread([this](){ + while (m_running) { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + dispatch(); + } + }) +{ + if (socketFd == -1) { + QByteArray socketName = wl_display_add_socket_auto(m_display); + qputenv("WAYLAND_DISPLAY", socketName); + } else { + wl_display_add_socket_fd(m_display, socketFd); + } + m_timer.start(); + Q_ASSERT(isClean()); +} + +CoreCompositor::~CoreCompositor() +{ + m_running = false; + m_dispatchThread.join(); + wl_display_destroy_clients(m_display); + wl_display_destroy(m_display); + qDebug() << "cleanup"; +} + +bool CoreCompositor::isClean() +{ + Lock lock(this); + for (auto *global : std::as_const(m_globals)) { + if (!global->isClean()) + return false; + } + return true; +} + +QString CoreCompositor::dirtyMessage() +{ + Lock lock(this); + QStringList messages; + for (auto *global : std::as_const(m_globals)) { + if (!global->isClean()) + messages << (global->metaObject()->className() % QLatin1String(": ") % global->dirtyMessage()); + } + return messages.join(", "); +} + +void CoreCompositor::dispatch(int timeout) +{ + Lock lock(this); + wl_display_flush_clients(m_display); + wl_event_loop_dispatch(m_eventLoop, timeout); +} + +/*! + * \brief Adds a new global interface for the compositor + * + * Takes ownership of \a global + */ +void CoreCompositor::add(Global *global) +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + m_globals.append(global); +} + +void CoreCompositor::remove(Global *global) +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + m_globals.removeAll(global); + delete global; +} + +uint CoreCompositor::nextSerial() +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + return wl_display_next_serial(m_display); +} + +uint CoreCompositor::currentTimeMilliseconds() +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + return uint(m_timer.elapsed()); +} + +wl_client *CoreCompositor::client(int index) +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + wl_list *clients = wl_display_get_client_list(m_display); + wl_client *client = nullptr; + int i = 0; + wl_client_for_each(client, clients) { + if (i++ == index) + return client; + } + return nullptr; +} + +void CoreCompositor::warnIfNotLockedByThread(const char *caller) +{ + if (!m_lock || !m_lock->isOwnedByCurrentThread()) { + qWarning() << caller << "called without locking the compositor to the current thread." + << "This means the compositor can start dispatching at any moment," + << "potentially leading to threading issues." + << "Unless you know what you are doing you should probably fix the test" + << "by locking the compositor before accessing it (see mutex())."; + } +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/corecompositor.h b/tests/auto/client/shared/corecompositor.h new file mode 100644 index 000000000..2df1b5758 --- /dev/null +++ b/tests/auto/client/shared/corecompositor.h @@ -0,0 +1,208 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_CORECOMPOSITOR_H +#define MOCKCOMPOSITOR_CORECOMPOSITOR_H + +#include <QtTest/QtTest> + +#include <wayland-server-core.h> + +struct wl_resource; + +namespace MockCompositor { + +class Global : public QObject +{ + Q_OBJECT +public: + virtual bool isClean() { return true; } + virtual QString dirtyMessage() { return isClean() ? "clean" : "dirty"; } +}; + +class CoreCompositor +{ +public: + enum CompositorType { + Default, + Legacy // wl-shell + }; + + CompositorType m_type = Default; + explicit CoreCompositor(CompositorType t = Default, int socketFd = -1); + + ~CoreCompositor(); + bool isClean(); + QString dirtyMessage(); + void dispatch(int timeout = 0); + + template<typename function_type, typename... arg_types> + auto exec(function_type func, arg_types&&... args) -> decltype(func()) + { + Lock lock(this); + return func(std::forward<arg_types>(args)...); + } + + template<typename function_type, typename... arg_types> + auto call(function_type func, arg_types&&... args) -> decltype(func()) + { + Lock lock(this); + auto boundFunc = std::bind(func, this); + return boundFunc(this, std::forward<arg_types>(args)...); + } + + // Unsafe section below, YOU are responsible that the compositor is locked or + // this is run through the mutex() method! + + void add(Global *global); + void remove(Global *global); + + /*! + * \brief Constructs and adds a new global with the given parameters + * + * Convenience function. i.e. + * + * compositor->add(new MyGlobal(compositor, version); + * + * can be written as: + * + * compositor->add<MyGlobal>(version); + * + * Returns the new global + */ + template<typename global_type, typename... arg_types> + global_type *add(arg_types&&... args) + { + warnIfNotLockedByThread(Q_FUNC_INFO); + auto *global = new global_type(this, std::forward<arg_types>(args)...); + m_globals.append(global); + return global; + } + + /*! + * \brief Removes all globals of the given type + * + * Convenience function + */ + template<typename global_type, typename... arg_types> + void removeAll() + { + const auto globals = getAll<global_type>(); + for (auto global : globals) + remove(global); + } + + /*! + * \brief Returns a global with the given type, if any + */ + template<typename global_type> + global_type *get() + { + warnIfNotLockedByThread(Q_FUNC_INFO); + for (auto *global : std::as_const(m_globals)) { + if (auto *casted = qobject_cast<global_type *>(global)) + return casted; + } + return nullptr; + } + + /*! + * \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 : std::as_const(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> + QList<global_type *> getAll() + { + warnIfNotLockedByThread(Q_FUNC_INFO); + QList<global_type *> matching; + for (auto *global : std::as_const(m_globals)) { + if (auto *casted = qobject_cast<global_type *>(global)) + matching.append(casted); + } + return matching; + } + + uint nextSerial(); + uint currentTimeMilliseconds(); + wl_client *client(int index = 0); + void warnIfNotLockedByThread(const char* caller = "warnIfNotLockedbyThread"); + +public: + // Only use this carefully from the test thread (i.e. lock first) + wl_display *m_display = nullptr; + +protected: + class Lock { + public: + explicit Lock(CoreCompositor *compositor) + : m_compositor(compositor) + , m_threadId(std::this_thread::get_id()) + { + // Can't use a QMutexLocker here, as it's not movable + compositor->m_mutex.lock(); + Q_ASSERT(compositor->m_lock == nullptr); + compositor->m_lock = this; + } + ~Lock() + { + Q_ASSERT(m_compositor->m_lock == this); + m_compositor->m_lock = nullptr; + m_compositor->m_mutex.unlock(); + } + + // Move semantics + Lock(Lock &&) = default; + Lock &operator=(Lock &&) = default; + + // Disable copying + Lock(const Lock &) = delete; + Lock &operator=(const Lock &) = delete; + + bool isOwnedByCurrentThread() const { return m_threadId == std::this_thread::get_id(); } + private: + CoreCompositor *m_compositor = nullptr; + std::thread::id m_threadId; + }; + wl_event_loop *m_eventLoop = nullptr; + bool m_running = true; + QList<Global *> m_globals; + QElapsedTimer m_timer; + +private: + Lock *m_lock = nullptr; + QMutex m_mutex; + std::thread m_dispatchThread; +}; + +template<typename container_type> +QByteArray toByteArray(container_type container) +{ + return QByteArray(reinterpret_cast<const char *>(container.data()), sizeof (container[0]) * container.size()); +} + +template<typename return_type> +return_type *fromResource(::wl_resource *resource) { + if (auto *r = return_type::Resource::fromResource(resource)) + return static_cast<return_type *>(r->object()); + return nullptr; +} + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_CORECOMPOSITOR_H diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp new file mode 100644 index 000000000..5d9c4e9a3 --- /dev/null +++ b/tests/auto/client/shared/coreprotocol.cpp @@ -0,0 +1,650 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "coreprotocol.h" +#include "datadevice.h" + +namespace MockCompositor { + +Surface::Surface(WlCompositor *wlCompositor, wl_client *client, int id, int version) + : QtWaylandServer::wl_surface(client, id, version) + , m_wlCompositor(wlCompositor) + , m_wlshell(wlCompositor->m_compositor->m_type == CoreCompositor::CompositorType::Legacy) +{ +} + +Surface::~Surface() +{ + // TODO: maybe make sure buffers are released? + qDeleteAll(m_commits); + if (m_wlShellSurface) + m_wlShellSurface->m_surface = nullptr; +} + +void Surface::sendFrameCallbacks() +{ + uint time = m_wlCompositor->m_compositor->currentTimeMilliseconds(); + for (auto *callback : m_waitingFrameCallbacks) + callback->sendDone(time); + m_waitingFrameCallbacks.clear(); +} + +void Surface::sendEnter(Output *output) +{ + m_outputs.append(output); + const auto outputResources = output->resourceMap().values(resource()->client()); + for (auto outputResource: outputResources) + wl_surface::send_enter(resource()->handle, outputResource->handle); +} + +void Surface::sendLeave(Output *output) +{ + m_outputs.removeOne(output); + const auto outputResources = output->resourceMap().values(resource()->client()); + for (auto outputResource: outputResources) + wl_surface::send_leave(resource()->handle, outputResource->handle); +} + +void Surface::map() +{ + m_mapped = true; +} + +void Surface::surface_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + for (auto *commit : m_commits) + delete commit->commitSpecific.frame; + bool removed = m_wlCompositor->m_surfaces.removeOne(this); + Q_ASSERT(removed); + delete this; +} + +void Surface::surface_destroy(Resource *resource) +{ + if (m_wlShellSurface) // on wl-shell the shell surface is automatically destroyed with the surface + wl_resource_destroy(m_wlShellSurface->resource()->handle); + Q_ASSERT(!m_wlShellSurface); + wl_resource_destroy(resource->handle); +} + +void Surface::surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y) +{ + Q_UNUSED(resource); + if (m_wlshell) { + m_buffer = buffer; + if (!buffer) + m_image = QImage(); + } else { + QPoint offset(x, y); + if (resource->version() < 5) + m_pending.commitSpecific.attachOffset = offset; + + m_pending.buffer = fromResource<Buffer>(buffer); + m_pending.commitSpecific.attached = true; + + emit attach(buffer, offset); + } +} + +void Surface::surface_set_buffer_scale(QtWaylandServer::wl_surface::Resource *resource, int32_t scale) +{ + Q_UNUSED(resource); + m_pending.bufferScale = scale; +} + +void Surface::surface_commit(Resource *resource) +{ + Q_UNUSED(resource); + + if (m_wlshell) { + if (m_buffer) { + struct ::wl_shm_buffer *shm_buffer = wl_shm_buffer_get(m_buffer); + if (shm_buffer) { + int stride = wl_shm_buffer_get_stride(shm_buffer); + uint format = wl_shm_buffer_get_format(shm_buffer); + Q_UNUSED(format); + void *data = wl_shm_buffer_get_data(shm_buffer); + const uchar *char_data = static_cast<const uchar *>(data); + QImage img(char_data, wl_shm_buffer_get_width(shm_buffer), wl_shm_buffer_get_height(shm_buffer), stride, QImage::Format_ARGB32_Premultiplied); + m_image = img; + } + } + + for (wl_resource *frameCallback : std::exchange(m_frameCallbackList, {})) { + auto time = m_wlCompositor->m_compositor->currentTimeMilliseconds(); + wl_callback_send_done(frameCallback, time); + wl_resource_destroy(frameCallback); + } + } else { + m_committed = m_pending; + m_commits.append(new DoubleBufferedState(m_committed)); + + if (auto *frame = m_pending.commitSpecific.frame) + m_waitingFrameCallbacks.append(frame); + + m_pending.commitSpecific = PerCommitData(); + emit commit(); + if (m_committed.commitSpecific.attached) + emit bufferCommitted(); + } +} + +void Surface::surface_frame(Resource *resource, uint32_t callback) +{ + if (m_wlshell) { + wl_resource *frameCallback = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); + m_frameCallbackList << frameCallback; + } else { + // Although valid, there is really no point having multiple frame requests in the same commit. + // Make sure we don't do it + QCOMPARE(m_pending.commitSpecific.frame, nullptr); + + auto *frame = new Callback(resource->client(), callback, 1); + m_pending.commitSpecific.frame = frame; + } +} + +void Surface::surface_offset(Resource *resource, int32_t x, int32_t y) +{ + Q_UNUSED(resource); + QPoint offset(x, y); + m_pending.commitSpecific.attachOffset = offset; +} + +bool WlCompositor::isClean() { + for (auto *surface : std::as_const(m_surfaces)) { + if (!CursorRole::fromSurface(surface)) { + if (m_compositor->m_type != CoreCompositor::CompositorType::Legacy) + return false; + if (surface->isMapped()) + return false; + } + } + return true; +} + +QString WlCompositor::dirtyMessage() +{ + if (isClean()) + return "clean"; + QStringList messages; + for (auto *s : std::as_const(m_surfaces)) { + QString role = s->m_role ? s->m_role->staticMetaObject.className(): "none/unknown"; + messages << "Surface with role: " + role; + } + return "Dirty, surfaces left:\n\t" + messages.join("\n\t"); +} + +void Output::sendGeometry() +{ + const auto resources = resourceMap().values(); + for (auto r : resources) + sendGeometry(r); +} + +void Output::sendGeometry(Resource *resource) +{ + // TODO: check resource version as well? + wl_output::send_geometry(resource->handle, + m_data.position.x(), m_data.position.y(), + m_data.physicalSize.width(), m_data.physicalSize.height(), + m_data.subpixel, m_data.make, m_data.model, m_data.transform); +} + +void Output::sendScale(int factor) +{ + Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION); + m_data.scale = factor; + const auto resources = resourceMap().values(); + for (auto r : resources) + sendScale(r); +} + +void Output::sendScale(Resource *resource) +{ + Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION); + // TODO: check resource version as well? + wl_output::send_scale(resource->handle, m_data.scale); +} + +void Output::sendDone(wl_client *client) +{ + Q_ASSERT(m_version >= WL_OUTPUT_DONE_SINCE_VERSION); + auto resources = resourceMap().values(client); + for (auto *r : resources) + wl_output::send_done(r->handle); +} + +void Output::sendDone() +{ + Q_ASSERT(m_version >= WL_OUTPUT_DONE_SINCE_VERSION); + // TODO: check resource version as well? + const auto resources = resourceMap().values(); + for (auto r : resources) + wl_output::send_done(r->handle); +} + +void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource) +{ + sendGeometry(resource); + send_mode(resource->handle, mode_preferred | mode_current, + m_data.mode.resolution.width(), m_data.mode.resolution.height(), m_data.mode.refreshRate); + if (m_version >= WL_OUTPUT_SCALE_SINCE_VERSION) + sendScale(resource); + + if (m_version >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output::send_done(resource->handle); + + Q_EMIT outputBound(resource); +} + +// Seat stuff +Seat::Seat(CoreCompositor *compositor, uint capabilities, int version) //TODO: check version + : QtWaylandServer::wl_seat(compositor->m_display, version) + , m_compositor(compositor) +{ + setCapabilities(capabilities); +} + +Seat::~Seat() +{ + qDeleteAll(m_oldPointers); + delete m_pointer; + + qDeleteAll(m_oldTouchs); + delete m_touch; + + qDeleteAll(m_oldKeyboards); + delete m_keyboard; +} + +void Seat::setCapabilities(uint capabilities) { + m_capabilities = capabilities; + + if (m_capabilities & capability_pointer) { + if (!m_pointer) + m_pointer = (new Pointer(this)); + } else if (m_pointer) { + m_oldPointers << m_pointer; + m_pointer = nullptr; + } + + if (m_capabilities & capability_touch) { + if (!m_touch) + m_touch = (new Touch(this)); + } else if (m_touch) { + m_oldTouchs << m_touch; + m_touch = nullptr; + } + + if (m_capabilities & capability_keyboard) { + if (!m_keyboard) + m_keyboard = (new Keyboard(this)); + } else if (m_keyboard) { + m_oldKeyboards << m_keyboard; + m_keyboard = nullptr; + } + + for (auto *resource : resourceMap()) + wl_seat::send_capabilities(resource->handle, capabilities); +} + +void Seat::seat_get_pointer(Resource *resource, uint32_t id) +{ + if (~m_capabilities & capability_pointer) { + qWarning() << "Client requested a wl_pointer without the capability being available." + << "This Could be a race condition when hotunplugging," + << "but is most likely a client error"; + Pointer *pointer = new Pointer(this); + pointer->add(resource->client(), id, resource->version()); + // TODO: mark as destroyed + m_oldPointers << pointer; + return; + } + m_pointer->add(resource->client(), id, resource->version()); +} + +void Seat::seat_get_touch(QtWaylandServer::wl_seat::Resource *resource, uint32_t id) +{ + if (~m_capabilities & capability_touch) { + qWarning() << "Client requested a wl_touch without the capability being available." + << "This Could be a race condition when hotunplugging," + << "but is most likely a client error"; + Touch *touch = new Touch(this); + touch->add(resource->client(), id, resource->version()); + // TODO: mark as destroyed + m_oldTouchs << touch; + return; + } + m_touch->add(resource->client(), id, resource->version()); +} + +void Seat::seat_get_keyboard(QtWaylandServer::wl_seat::Resource *resource, uint32_t id) +{ + if (~m_capabilities & capability_keyboard) { + qWarning() << "Client requested a wl_keyboard without the capability being available." + << "This Could be a race condition when hotunplugging," + << "but is most likely a client error"; + Keyboard *keyboard = new Keyboard(this); + keyboard->add(resource->client(), id, resource->version()); + // TODO: mark as destroyed + m_oldKeyboards << keyboard; + return; + } + m_keyboard->add(resource->client(), id, resource->version()); +} + +Surface *Pointer::cursorSurface() +{ + return m_cursorRole ? m_cursorRole->m_surface : nullptr; +} + +uint Pointer::sendEnter(Surface *surface, const QPointF &position) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + + uint serial = m_seat->m_compositor->nextSerial(); + m_enterSerials << serial; + m_cursorRole.clear(); // According to the protocol, the pointer image is undefined after enter + + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) { + wl_pointer::send_enter(r->handle, serial, surface->resource()->handle, x ,y); + } + return serial; +} + +uint Pointer::sendLeave(Surface *surface) +{ + uint serial = m_seat->m_compositor->nextSerial(); + + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + wl_pointer::send_leave(r->handle, serial, surface->resource()->handle); + return serial; +} + +// Make sure you call enter, frame etc. first +void Pointer::sendMotion(wl_client *client, const QPointF &position) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_motion(r->handle, time, x, y); +} + +// Make sure you call enter, frame etc. first +uint Pointer::sendButton(wl_client *client, uint button, uint state) +{ + Q_ASSERT(state == button_state_pressed || state == button_state_released); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + uint serial = m_seat->m_compositor->nextSerial(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_button(r->handle, serial, time, button, state); + return serial; +} + +// Make sure you call enter, frame etc. first +void Pointer::sendAxis(wl_client *client, axis axis, qreal value) +{ + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + wl_fixed_t val = wl_fixed_from_double(value); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + 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::sendAxisValue120(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int value120) +{ + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_value120(r->handle, axis, value120); +} + +void Pointer::sendAxisRelativeDirection(wl_client *client, QtWaylandServer::wl_pointer::axis axis, QtWaylandServer::wl_pointer::axis_relative_direction direction) +{ + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_relative_direction(r->handle, axis, direction); +} + +void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) +{ + Q_UNUSED(resource); + + if (!surface) { + m_cursorRole = nullptr; + } else { + auto *s = fromResource<Surface>(surface); + QVERIFY(s); + if (s->m_role) { + m_cursorRole = CursorRole::fromSurface(s); + QVERIFY(m_cursorRole); + } else { + m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole + s->m_role = m_cursorRole; + } + } + + // Directly checking the last serial would be racy, we may just have sent leaves/enters which + // the client hasn't yet seen. Instead just check if the serial matches an enter serial since + // the last time the client sent a set_cursor request. + QVERIFY(m_enterSerials.contains(serial)); + while (m_enterSerials.first() < serial) { m_enterSerials.removeFirst(); } + + m_hotspot = QPoint(hotspot_x, hotspot_y); + emit setCursor(serial); +} + +uint Touch::sendDown(Surface *surface, const QPointF &position, int id) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + uint serial = m_seat->m_compositor->nextSerial(); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + wl_client *client = surface->resource()->client(); + + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + wl_touch::send_down(r->handle, serial, time, surface->resource()->handle, id, x, y); + + return serial; +} + +uint Touch::sendUp(wl_client *client, int id) +{ + uint serial = m_seat->m_compositor->nextSerial(); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + wl_touch::send_up(r->handle, serial, time, id); + + return serial; +} + +void Touch::sendMotion(wl_client *client, const QPointF &position, int id) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + wl_touch::send_motion(r->handle, time, id, x, y); +} + +void Touch::sendFrame(wl_client *client) +{ + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + send_frame(r->handle); +} + +void Touch::sendCancel(wl_client *client) +{ + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + send_cancel(r->handle); +} + +uint Keyboard::sendEnter(Surface *surface) +{ + auto serial = m_seat->m_compositor->nextSerial(); + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_enter(r->handle, serial, surface->resource()->handle, QByteArray()); + m_enteredSurface = surface; + return serial; +} + +uint Keyboard::sendLeave(Surface *surface) +{ + auto serial = m_seat->m_compositor->nextSerial(); + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_leave(r->handle, serial, surface->resource()->handle); + m_enteredSurface = nullptr; + return serial; +} + +uint Keyboard::sendKey(wl_client *client, uint key, uint state) +{ + Q_ASSERT(state == key_state_pressed || state == key_state_released); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + uint serial = m_seat->m_compositor->nextSerial(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) { + send_key(r->handle, serial, time, key, state); + } + return serial; +} + +// Shm implementation +Shm::Shm(CoreCompositor *compositor, QList<format> formats, int version) + : QtWaylandServer::wl_shm(compositor->m_display, version) + , m_compositor(compositor) + , m_formats(formats) +{ + // Some formats are specified as mandatory + Q_ASSERT(m_formats.contains(format_argb8888)); + Q_ASSERT(m_formats.contains(format_xrgb8888)); +} + +bool Shm::isClean() +{ +// for (ShmPool *pool : std::as_const(m_pools)) { +// //TODO: return false if not cursor buffer +// if (pool->m_buffers.isEmpty()) { +// return false; +// } +// } + return true; +} + +void Shm::shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size) +{ + Q_UNUSED(fd); + Q_UNUSED(size); + auto *pool = new ShmPool(this, resource->client(), id, 1); + m_pools.append(pool); +} + +ShmPool::ShmPool(Shm *shm, wl_client *client, int id, int version) + : QtWaylandServer::wl_shm_pool(client, id, version) + , m_shm(shm) +{ +} + +void ShmPool::shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) +{ + QSize size(width, height); + new ShmBuffer(offset, size, stride, Shm::format(format), resource->client(), id); +} + +void ShmPool::shm_pool_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + bool removed = m_shm->m_pools.removeOne(this); + Q_ASSERT(removed); + delete this; +} + +WlShell::WlShell(CoreCompositor *compositor, int version) + : QtWaylandServer::wl_shell(compositor->m_display, version) + , m_compositor(compositor) +{ +} + +void WlShell::shell_get_shell_surface(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource<Surface>(surface); + auto *wlShellSurface = new WlShellSurface(this, resource->client(), id, s); + m_wlShellSurfaces << wlShellSurface; + emit wlShellSurfaceCreated(wlShellSurface); +} + +WlShellSurface::WlShellSurface(WlShell *wlShell, wl_client *client, int id, Surface *surface) + : QtWaylandServer::wl_shell_surface(client, id, 1) + , m_wlShell(wlShell) + , m_surface(surface) +{ + surface->m_wlShellSurface = this; + surface->map(); +} + +WlShellSurface::~WlShellSurface() +{ + if (m_surface) + m_surface->m_wlShellSurface = nullptr; +} + +void WlShellSurface::sendConfigure(uint32_t edges, int32_t width, int32_t height) +{ + wl_shell_surface::send_configure(edges, width, height); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h new file mode 100644 index 000000000..bea39dd13 --- /dev/null +++ b/tests/auto/client/shared/coreprotocol.h @@ -0,0 +1,464 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_COREPROTOCOL_H +#define MOCKCOMPOSITOR_COREPROTOCOL_H + +#include "corecompositor.h" + +#include <QtCore/qpointer.h> + +#include <qwayland-server-wayland.h> + +namespace MockCompositor { + +class WlCompositor; +class WlShell; +class WlShellSurface; +class Output; +class Pointer; +class Touch; +class Keyboard; +class CursorRole; +class ShmPool; +class ShmBuffer; +class DataDevice; + +class Buffer : public QObject, public QtWaylandServer::wl_buffer +{ + Q_OBJECT +public: + explicit Buffer(wl_client *client, int id, int version) + : QtWaylandServer::wl_buffer(client, id, version) + { + } + virtual QSize size() const = 0; + bool m_destroyed = false; + +protected: + void buffer_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + m_destroyed = true; + // The client side resource has been destroyed, but we keep this object because it may be + // be used as a reference by e.g. surface for the currently committed buffer so it's not + // yet safe to free it. + + //TODO: The memory should be freed by its factory + } + void buffer_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } +}; + +class Callback : public QObject, public QtWaylandServer::wl_callback +{ + Q_OBJECT +public: + explicit Callback(wl_client *client, int id, int version = 1) + : QtWaylandServer::wl_callback(client, id, version) + { + } + ~Callback() override { if (!m_destroyed) wl_resource_destroy(resource()->handle); } + void send_done(uint32_t data) = delete; // use state-tracking method below instead + void sendDone(uint data) { Q_ASSERT(!m_done); QtWaylandServer::wl_callback::send_done(data); m_done = true; } + void sendDoneAndDestroy(uint data) { sendDone(data); wl_resource_destroy(resource()->handle); } + bool m_done = false; + bool m_destroyed = false; +protected: + void callback_destroy_resource(Resource *resource) override { Q_UNUSED(resource); m_destroyed = true; } +}; + +class SurfaceRole : public QObject { + Q_OBJECT +}; + +class Surface : public QObject, public QtWaylandServer::wl_surface +{ + Q_OBJECT +public: + explicit Surface(WlCompositor *wlCompositor, wl_client *client, int id, int version); + ~Surface() override; + void sendFrameCallbacks(); + void sendEnter(Output *output); + void send_enter(::wl_resource *output) = delete; + void sendLeave(Output *output); + void send_leave(::wl_resource *output) = delete; + + void map(); + bool isMapped() const { return m_mapped; } + WlShellSurface *wlShellSurface() const { return m_wlShellSurface; } + + WlCompositor *m_wlCompositor; + struct PerCommitData { + Callback *frame = nullptr; + QPoint attachOffset; + bool attached = false; + }; + struct DoubleBufferedState { + PerCommitData commitSpecific; + Buffer *buffer = nullptr; + uint configureSerial = 0; + int bufferScale = 1; + } m_pending, m_committed; + QList<DoubleBufferedState *> m_commits; + QList<Callback *> m_waitingFrameCallbacks; + QList<Output *> m_outputs; + SurfaceRole *m_role = nullptr; + + WlShellSurface *m_wlShellSurface = nullptr; + bool m_mapped = false; + QList<wl_resource *> m_frameCallbackList; + + wl_resource *m_buffer = nullptr; + QImage m_image; // checking backingStore + bool m_wlshell = false; + +signals: + void attach(void *buffer, QPoint offset); + void commit(); + void bufferCommitted(); + +protected: + void surface_destroy_resource(Resource *resource) override; + void surface_destroy(Resource *resource) override; + void surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y) override; + void surface_set_buffer_scale(Resource *resource, int32_t scale) override; + void surface_commit(Resource *resource) override; + void surface_frame(Resource *resource, uint32_t callback) override; + void surface_offset(Resource *resource, int32_t x, int32_t y) override; +}; + +class Region : public QtWaylandServer::wl_region +{ +public: + explicit Region(wl_client *client, int id, int version) + : QtWaylandServer::wl_region(client, id, version) + { + } + + void region_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } +}; + +class WlCompositor : public Global, public QtWaylandServer::wl_compositor +{ + Q_OBJECT +public: + explicit WlCompositor(CoreCompositor *compositor, int version = 6) + : QtWaylandServer::wl_compositor(compositor->m_display, version) + , m_compositor(compositor) + {} + bool isClean() override; + QString dirtyMessage() override; + QList<Surface *> m_surfaces; + CoreCompositor *m_compositor = nullptr; + +signals: + void surfaceCreated(Surface *surface); + +protected: + void compositor_create_surface(Resource *resource, uint32_t id) override + { + auto *surface = new Surface(this, resource->client(), id, resource->version()); + m_surfaces.append(surface); + emit surfaceCreated(surface); + } + + void compositor_create_region(Resource *resource, uint32_t id) override + { + new Region(resource->client(), id, resource->version()); + } +}; + +class WlShell : public Global, public QtWaylandServer::wl_shell +{ + Q_OBJECT +public: + explicit WlShell(CoreCompositor *compositor, int version = 1); + QList<WlShellSurface *> m_wlShellSurfaces; + CoreCompositor *m_compositor = nullptr; + +signals: + void wlShellSurfaceCreated(WlShellSurface *wlShellSurface); + +protected: + void shell_get_shell_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override; +}; + +class WlShellSurface : public QObject, public QtWaylandServer::wl_shell_surface +{ + Q_OBJECT +public: + explicit WlShellSurface(WlShell *wlShell, wl_client *client, int id, Surface *surface); + ~WlShellSurface() override; + void sendConfigure(uint32_t edges, int32_t width, int32_t height); + void send_configure(uint32_t edges, int32_t width, int32_t height) = delete; + + void shell_surface_destroy_resource(Resource *) override { delete this; } + + WlShell *m_wlShell = nullptr; + Surface *m_surface = nullptr; +}; + +class Subsurface : public QObject, public QtWaylandServer::wl_subsurface +{ + Q_OBJECT +public: + explicit Subsurface(wl_client *client, int id, int version) + : QtWaylandServer::wl_subsurface(client, id, version) + { + } +}; + +class SubCompositor : public Global, public QtWaylandServer::wl_subcompositor +{ + Q_OBJECT +public: + explicit SubCompositor(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::wl_subcompositor(compositor->m_display, version) + {} + QList<Subsurface *> m_subsurfaces; + +signals: + void subsurfaceCreated(Subsurface *subsurface); + +protected: + void subcompositor_get_subsurface(Resource *resource, uint32_t id, ::wl_resource *surface, ::wl_resource *parent) override + { + QTRY_VERIFY(parent); + QTRY_VERIFY(surface); + auto *subsurface = new Subsurface(resource->client(), id, resource->version()); + m_subsurfaces.append(subsurface); // TODO: clean up? + emit subsurfaceCreated(subsurface); + } +}; + +struct OutputMode { + explicit OutputMode() = default; + explicit OutputMode(const QSize &resolution, int refreshRate = 60000) + : resolution(resolution), refreshRate(refreshRate) + {} + QSize resolution = QSize(1920, 1080); + int refreshRate = 60000; // In mHz + //TODO: flags (they're currently hard-coded) + + // in mm + QSize physicalSizeForDpi(int dpi) { return (QSizeF(resolution) * 25.4 / dpi).toSize(); } +}; + +struct OutputData { + using Subpixel = QtWaylandServer::wl_output::subpixel; + using Transform = QtWaylandServer::wl_output::transform; + explicit OutputData() = default; + + // for geometry event + QPoint position; + QSize physicalSize = QSize(0, 0); // means unknown physical size + QString make = "Make"; + QString model = "Model"; + Subpixel subpixel = Subpixel::subpixel_unknown; + Transform transform = Transform::transform_normal; + + int scale = 1; // for scale event + OutputMode mode; // for mode event +}; + +class Output : public Global, public QtWaylandServer::wl_output +{ + Q_OBJECT +public: + explicit Output(CoreCompositor *compositor, OutputData data = OutputData(), int version = 2) + : QtWaylandServer::wl_output(compositor->m_display, version) + , m_data(std::move(data)) + , m_version(version) + {} + + void send_geometry() = delete; + void sendGeometry(); + void sendGeometry(Resource *resource); // Sends to only one client + + void send_scale(int32_t factor) = delete; + void sendScale(int factor); + void sendScale(Resource *resource); // Sends current scale to only one client + + void sendDone(wl_client *client); + void sendDone(); + + int scale() const { return m_data.scale; } + + OutputData m_data; + int m_version = 1; // TODO: remove on libwayland upgrade + +Q_SIGNALS: + void outputBound(Resource *resource); + +protected: + void output_bind_resource(Resource *resource) override; +}; + +class Seat : public Global, public QtWaylandServer::wl_seat +{ + Q_OBJECT +public: + explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch, int version = 9); + ~Seat() override; + void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead + void send_capabilities(uint capabilities) = delete; // Use wrapper instead + void setCapabilities(uint capabilities); + + CoreCompositor *m_compositor = nullptr; + + Pointer* m_pointer = nullptr; + QList<Pointer *> m_oldPointers; + + Touch* m_touch = nullptr; + QList<Touch *> m_oldTouchs; + + Keyboard* m_keyboard = nullptr; + QList<Keyboard *> m_oldKeyboards; + + uint m_capabilities = 0; + +protected: + void seat_bind_resource(Resource *resource) override + { + wl_seat::send_capabilities(resource->handle, m_capabilities); + } + + void seat_get_pointer(Resource *resource, uint32_t id) override; + void seat_get_touch(Resource *resource, uint32_t id) override; + void seat_get_keyboard(Resource *resource, uint32_t id) override; + +// void seat_release(Resource *resource) override; +}; + +class Pointer : public QObject, public QtWaylandServer::wl_pointer +{ + Q_OBJECT +public: + explicit Pointer(Seat *seat) : m_seat(seat) {} + Surface *cursorSurface(); + QPointer<CursorRole> m_cursorRole; + void send_enter() = delete; + uint sendEnter(Surface *surface, const QPointF &position); + void send_leave() = delete; + uint sendLeave(Surface *surface); + 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); + void sendAxisValue120(wl_client *client, axis axis, int value120); + void sendAxisRelativeDirection(wl_client *client, axis axis, axis_relative_direction direction); + + Seat *m_seat = nullptr; + QList<uint> m_enterSerials; + QPoint m_hotspot; + +signals: + void setCursor(uint serial); //TODO: add arguments? + +protected: + void pointer_set_cursor(Resource *resource, uint32_t serial, ::wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) override; + //TODO +}; + +class CursorRole : public SurfaceRole { + Q_OBJECT +public: + explicit CursorRole(Surface *surface) // TODO: needs some more args + : m_surface(surface) + { + connect(m_surface, &QObject::destroyed, this, &QObject::deleteLater); + } + static CursorRole *fromSurface(Surface *surface) { return qobject_cast<CursorRole *>(surface->m_role); } + Surface *m_surface = nullptr; +}; + +class Touch : public QObject, public QtWaylandServer::wl_touch +{ + Q_OBJECT +public: + explicit Touch(Seat *seat) : m_seat(seat) {} + uint sendDown(Surface *surface, const QPointF &position, int id); + uint sendUp(wl_client *client, int id); + void sendMotion(wl_client *client, const QPointF &position, int id); + void sendFrame(wl_client *client); + void sendCancel(wl_client *client); + + Seat *m_seat = nullptr; +}; + +class Keyboard : public QObject, public QtWaylandServer::wl_keyboard +{ + Q_OBJECT +public: + explicit Keyboard(Seat *seat) : m_seat(seat) {} + //TODO: Keymap event + uint sendEnter(Surface *surface); + 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 +{ + Q_OBJECT +public: + explicit Shm(CoreCompositor *compositor, QList<format> formats = {format_argb8888, format_xrgb8888, format_rgb888}, int version = 1); + bool isClean() override; + CoreCompositor *m_compositor = nullptr; + QList<ShmPool *> m_pools; + const QList<format> m_formats; + +protected: + void shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size) override; + void shm_bind_resource(Resource *resource) override + { + for (auto format : std::as_const(m_formats)) + send_format(resource->handle, format); + } +}; + +class ShmPool : QObject, public QtWaylandServer::wl_shm_pool +{ + Q_OBJECT +public: + explicit ShmPool(Shm *shm, wl_client *client, int id, int version = 1); + Shm *m_shm = nullptr; + QList<ShmBuffer *> m_buffers; + +protected: + void shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) override; + void shm_pool_destroy_resource(Resource *resource) override; + void shm_pool_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } +}; + +class ShmBuffer : public Buffer +{ + Q_OBJECT +public: + static ShmBuffer *fromBuffer(Buffer *buffer) { return qobject_cast<ShmBuffer *>(buffer); } + explicit ShmBuffer(int offset, const QSize &size, int stride, Shm::format format, wl_client *client, int id, int version = 1) + : Buffer(client, id, version) + , m_offset(offset) + , m_size(size) + , m_stride(stride) + , m_format(format) + { + } + QSize size() const override { return m_size; } + const int m_offset; + const QSize m_size; + const int m_stride; + const Shm::format m_format; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_COREPROTOCOL_H diff --git a/tests/auto/client/shared/datadevice.cpp b/tests/auto/client/shared/datadevice.cpp new file mode 100644 index 000000000..efb88d0b1 --- /dev/null +++ b/tests/auto/client/shared/datadevice.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "datadevice.h" + +namespace MockCompositor { + +bool DataDeviceManager::isClean() +{ + for (auto *device : std::as_const(m_dataDevices)) { + // The client should not leak selection offers, i.e. if this fails, there is a missing + // data_offer.destroy request + if (!device->m_sentSelectionOffers.empty()) + return false; + } + return true; +} + +DataDevice *DataDeviceManager::deviceFor(Seat *seat) +{ + Q_ASSERT(seat); + if (auto *device = m_dataDevices.value(seat, nullptr)) + return device; + + auto *device = new DataDevice(this, seat); + m_dataDevices[seat] = device; + return device; +} + +void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, wl_resource *seatResource) +{ + auto *seat = fromResource<Seat>(seatResource); + QVERIFY(seat); + auto *device = deviceFor(seat); + device->add(resource->client(), id, resource->version()); +} + +void DataDeviceManager::data_device_manager_create_data_source(Resource *resource, uint32_t id) +{ + new QtWaylandServer::wl_data_source(resource->client(), id, 1); +} + +DataDevice::~DataDevice() +{ + // If the client(s) hasn't deleted the wayland object, just ignore subsequent events + for (auto *r : resourceMap()) + wl_resource_set_implementation(r->handle, nullptr, nullptr, nullptr); +} + +DataOffer *DataDevice::sendDataOffer(wl_client *client, const QStringList &mimeTypes) +{ + Q_ASSERT(client); + auto *offer = new DataOffer(this, client, m_manager->m_version); + m_offer = offer; + for (auto *resource : resourceMap().values(client)) + wl_data_device::send_data_offer(resource->handle, offer->resource()->handle); + for (const auto &mimeType : mimeTypes) + offer->send_offer(mimeType); + return offer; +} + +void DataDevice::sendSelection(DataOffer *offer) +{ + auto *client = offer->resource()->client(); + for (auto *resource : resourceMap().values(client)) + wl_data_device::send_selection(resource->handle, offer->resource()->handle); + m_sentSelectionOffers << offer; +} + +void DataDevice::sendEnter(Surface *surface, const QPoint &position) +{ + uint serial = m_manager->m_compositor->nextSerial(); + Resource *resource = resourceMap().value(surface->resource()->client()); + if (m_offer) + wl_data_device::send_enter(resource->handle, serial, surface->resource()->handle, position.x(), position.y(), m_offer->resource()->handle); +} + +void DataDevice::sendMotion(Surface *surface, const QPoint &position) +{ + uint32_t time = m_manager->m_compositor->nextSerial(); + Resource *resource = resourceMap().value(surface->resource()->client()); + wl_data_device::send_motion(resource->handle, time, position.x(), position.y()); +} + +void DataDevice::sendDrop(Surface *surface) +{ + Resource *resource = resourceMap().value(surface->resource()->client()); + wl_data_device::send_drop(resource->handle); +} + +void DataDevice::sendLeave(Surface *surface) +{ + Resource *resource = resourceMap().value(surface->resource()->client()); + wl_data_device::send_leave(resource->handle); +} + +void DataOffer::data_offer_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + delete this; +} + +void DataOffer::data_offer_receive(Resource *resource, const QString &mime_type, int32_t fd) +{ + Q_UNUSED(resource); + emit receive(mime_type, fd); +} + +void DataOffer::data_offer_destroy(QtWaylandServer::wl_data_offer::Resource *resource) +{ + m_dataDevice->m_sentSelectionOffers.removeOne(this); + wl_resource_destroy(resource->handle); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/datadevice.h b/tests/auto/client/shared/datadevice.h new file mode 100644 index 000000000..356dfa74a --- /dev/null +++ b/tests/auto/client/shared/datadevice.h @@ -0,0 +1,113 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_DATADEVICE_H +#define MOCKCOMPOSITOR_DATADEVICE_H + +//TODO: need this? +#include "coreprotocol.h" + +namespace MockCompositor { + +class DataOffer; + +class DataDeviceManager : public Global, public QtWaylandServer::wl_data_device_manager +{ + Q_OBJECT +public: + explicit DataDeviceManager(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::wl_data_device_manager(compositor->m_display, version) + , m_version(version) + , m_compositor(compositor) + {} + ~DataDeviceManager() override { qDeleteAll(m_dataDevices); } + bool isClean() override; + DataDevice *deviceFor(Seat *seat); + + int m_version = 1; // TODO: remove on libwayland upgrade + QMap<Seat *, DataDevice *> m_dataDevices; + CoreCompositor *m_compositor; + +protected: + void data_device_manager_get_data_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override; + void data_device_manager_create_data_source(Resource *resource, uint32_t id) override; +}; + +class DataDevice : public QObject, public QtWaylandServer::wl_data_device +{ + Q_OBJECT +public: + explicit DataDevice(DataDeviceManager *manager, Seat *seat) + : m_manager(manager) + , m_seat(seat) + {} + ~DataDevice() override; + void send_data_offer(::wl_resource *resource) = delete; + DataOffer *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {}); + + void send_selection(::wl_resource *resource) = delete; + void sendSelection(DataOffer *offer); + + void send_enter(uint32_t serial, ::wl_resource *surface, wl_fixed_t x, wl_fixed_t y, ::wl_resource *id) = delete; + void sendEnter(Surface *surface, const QPoint& position); + + void send_motion(uint32_t time, wl_fixed_t x, wl_fixed_t y) = delete; + void sendMotion(Surface *surface, const QPoint &position); + + void send_drop(::wl_resource *resource) = delete; + void sendDrop(Surface *surface); + + void send_leave(::wl_resource *resource) = delete; + void sendLeave(Surface *surface); + + DataDeviceManager *m_manager = nullptr; + Seat *m_seat = nullptr; + QList<DataOffer *> m_sentSelectionOffers; + QPointer<DataOffer> m_offer; + +signals: + void dragStarted(); + +protected: + void data_device_start_drag(Resource *resource, ::wl_resource *source, ::wl_resource *origin, ::wl_resource *icon, uint32_t serial) override + { + Q_UNUSED(resource); + Q_UNUSED(source); + Q_UNUSED(origin); + Q_UNUSED(icon); + Q_UNUSED(serial); + emit dragStarted(); + } + + void data_device_release(Resource *resource) override + { + int removed = m_manager->m_dataDevices.remove(m_seat); + QVERIFY(removed); + wl_resource_destroy(resource->handle); + } +}; + +class DataOffer : public QObject, public QtWaylandServer::wl_data_offer +{ + Q_OBJECT +public: + explicit DataOffer(DataDevice *dataDevice, ::wl_client *client, int version) + : QtWaylandServer::wl_data_offer (client, 0, version) + , m_dataDevice(dataDevice) + {} + + DataDevice *m_dataDevice = nullptr; + +signals: + void receive(QString mimeType, int fd); + +protected: + void data_offer_destroy_resource(Resource *resource) override; + void data_offer_receive(Resource *resource, const QString &mime_type, int32_t fd) override; +// void data_offer_accept(Resource *resource, uint32_t serial, const QString &mime_type) override; + void data_offer_destroy(Resource *resource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_DATADEVICE_H diff --git a/tests/auto/client/shared/fractionalscalev1.cpp b/tests/auto/client/shared/fractionalscalev1.cpp new file mode 100644 index 000000000..28c778025 --- /dev/null +++ b/tests/auto/client/shared/fractionalscalev1.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "fractionalscalev1.h" + +namespace MockCompositor { + +FractionalScaleManager::FractionalScaleManager(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_fractional_scale_manager_v1(compositor->m_display, version) +{ +} + +void FractionalScaleManager::wp_fractional_scale_manager_v1_get_fractional_scale(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource<Surface>(surface); + auto *scaler = new FractionalScale(s, resource->client(), id, resource->version()); + connect(scaler, &QObject::destroyed, this, [this, scaler]() { + m_fractionalScales.removeOne(scaler); + }); + m_fractionalScales << scaler; +} + +FractionalScale::FractionalScale(Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::wp_fractional_scale_v1(client, id, version) + , m_surface(surface) +{ +} + +void FractionalScale::wp_fractional_scale_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void FractionalScale::wp_fractional_scale_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} diff --git a/tests/auto/client/shared/fractionalscalev1.h b/tests/auto/client/shared/fractionalscalev1.h new file mode 100644 index 000000000..1ae2fad1f --- /dev/null +++ b/tests/auto/client/shared/fractionalscalev1.h @@ -0,0 +1,39 @@ +// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_FRACTIONALSCALE_H +#define MOCKCOMPOSITOR_FRACTIONALSCALE_H + +#include "coreprotocol.h" +#include <qwayland-server-fractional-scale-v1.h> + +namespace MockCompositor { + +class FractionalScale; + +class FractionalScaleManager : public Global, public QtWaylandServer::wp_fractional_scale_manager_v1 +{ + Q_OBJECT +public: + explicit FractionalScaleManager(CoreCompositor *compositor, int version = 1); + QList<FractionalScale *> m_fractionalScales; + +protected: + void wp_fractional_scale_manager_v1_get_fractional_scale(Resource *resource, uint32_t id, wl_resource *surface) override; +}; + +class FractionalScale : public QObject, public QtWaylandServer::wp_fractional_scale_v1 +{ + Q_OBJECT +public: + explicit FractionalScale(Surface *surface, wl_client *client, int id, int version); + Surface *m_surface; + +protected: + void wp_fractional_scale_v1_destroy_resource(Resource *resource) override; + void wp_fractional_scale_v1_destroy(Resource *resource) override; +}; + +} + +#endif diff --git a/tests/auto/client/shared/fullscreenshellv1.cpp b/tests/auto/client/shared/fullscreenshellv1.cpp new file mode 100644 index 000000000..24468e14b --- /dev/null +++ b/tests/auto/client/shared/fullscreenshellv1.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "fullscreenshellv1.h" + +namespace MockCompositor { + +FullScreenShellV1::FullScreenShellV1(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void FullScreenShellV1::zwp_fullscreen_shell_v1_present_surface(Resource *resource, struct ::wl_resource *surface, uint32_t method, struct ::wl_resource *output) +{ + Q_UNUSED(resource); + Q_UNUSED(method); + Q_UNUSED(output); + + m_surfaces.append(fromResource<Surface>(surface)); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/fullscreenshellv1.h b/tests/auto/client/shared/fullscreenshellv1.h new file mode 100644 index 000000000..02ebf1e0c --- /dev/null +++ b/tests/auto/client/shared/fullscreenshellv1.h @@ -0,0 +1,34 @@ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_FULLSCREENSHELLV1_H +#define MOCKCOMPOSITOR_FULLSCREENSHELLV1_H + +#include "coreprotocol.h" +#include <qwayland-server-fullscreen-shell-unstable-v1.h> + +#include <QList> + +namespace MockCompositor { + +class Surface; +class FullScreenShellV1; + +class FullScreenShellV1 : public Global, public QtWaylandServer::zwp_fullscreen_shell_v1 +{ + Q_OBJECT +public: + explicit FullScreenShellV1(CoreCompositor *compositor); + + QList<Surface *> surfaces() const { return m_surfaces; } + +protected: + void zwp_fullscreen_shell_v1_present_surface(Resource *resource, struct ::wl_resource *surface, uint32_t method, struct ::wl_resource *output) override; + +private: + QList<Surface *> m_surfaces; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_FULLSCREENSHELLV1_H diff --git a/tests/auto/client/shared/iviapplication.cpp b/tests/auto/client/shared/iviapplication.cpp new file mode 100644 index 000000000..f4f167600 --- /dev/null +++ b/tests/auto/client/shared/iviapplication.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "iviapplication.h" + +namespace MockCompositor { + +IviApplication::IviApplication(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void IviApplication::ivi_application_surface_create(Resource *resource, uint32_t ivi_id, struct ::wl_resource *surface, uint32_t id) +{ + auto *s = fromResource<Surface>(surface); + auto *iviSurface = new IviSurface(this, s, ivi_id, resource->client(), id, resource->version()); + m_iviSurfaces << iviSurface; + qDebug() << "count is " << m_iviSurfaces.size(); +} + +IviSurface::IviSurface(IviApplication *iviApplication, Surface *surface, uint32_t ivi_id, wl_client *client, int id, int version) + : QtWaylandServer::ivi_surface(client, id, version) + , m_iviId(ivi_id) + , m_iviApplication(iviApplication) + , m_surface(surface) +{ + surface->map(); +} + +void IviSurface::ivi_surface_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + bool removed = m_iviApplication->m_iviSurfaces.removeOne(this); + Q_ASSERT(removed); + qDebug() << "destroy"; + + delete this; +} + +void IviSurface::ivi_surface_destroy(QtWaylandServer::ivi_surface::Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/iviapplication.h b/tests/auto/client/shared/iviapplication.h new file mode 100644 index 000000000..1644d0cfd --- /dev/null +++ b/tests/auto/client/shared/iviapplication.h @@ -0,0 +1,50 @@ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_IVIAPPLICATION_H +#define MOCKCOMPOSITOR_IVIAPPLICATION_H + +#include "coreprotocol.h" +#include <qwayland-server-ivi-application.h> + +#include <QList> + +namespace MockCompositor { + +class Surface; +class IviApplication; +class IviSurface; + +class IviApplication : public Global, public QtWaylandServer::ivi_application +{ + Q_OBJECT +public: + explicit IviApplication(CoreCompositor *compositor); + + QList<IviSurface *> m_iviSurfaces; +protected: + void ivi_application_surface_create(Resource *resource, uint32_t ivi_id, struct ::wl_resource *surface, uint32_t id) override; + +}; + +class IviSurface : public QObject, public QtWaylandServer::ivi_surface +{ + Q_OBJECT +public: + IviSurface(IviApplication *iviApplication, Surface *surface, uint32_t ivi_id, wl_client *client, int id, int version); + + Surface *surface() const { return m_surface; } + + void ivi_surface_destroy_resource(Resource *resource) override; + void ivi_surface_destroy(Resource *resource) override; + + const uint m_iviId = 0; +private: + IviApplication *m_iviApplication; + Surface *m_surface = nullptr; +}; + + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_IVIAPPLICATION_H diff --git a/tests/auto/client/shared/mockcompositor.cpp b/tests/auto/client/shared/mockcompositor.cpp index 797c05c44..bbf406d64 100644 --- a/tests/auto/client/shared/mockcompositor.cpp +++ b/tests/auto/client/shared/mockcompositor.cpp @@ -1,503 +1,143 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockcompositor.h" -#include "mockinput.h" -#include "mockoutput.h" -#include "mocksurface.h" -#include "mockwlshell.h" -#include "mockxdgshellv6.h" -#include "mockiviapplication.h" -#include <wayland-xdg-shell-unstable-v6-server-protocol.h> - -#include <stdio.h> -MockCompositor::MockCompositor() -{ - pthread_create(&m_thread, 0, run, this); - - m_mutex.lock(); - m_waitCondition.wait(&m_mutex); - m_mutex.unlock(); -} - -MockCompositor::~MockCompositor() -{ - m_alive = false; - m_waitCondition.wakeOne(); - pthread_join(m_thread, 0); -} - -void MockCompositor::lock() -{ - m_mutex.lock(); -} - -void MockCompositor::unlock() -{ - m_mutex.unlock(); -} - -void MockCompositor::applicationInitialized() -{ - m_ready = true; -} - -int MockCompositor::waylandFileDescriptor() const -{ - return m_compositor->fileDescriptor(); -} - -void MockCompositor::processWaylandEvents() -{ - m_waitCondition.wakeOne(); -} - -void MockCompositor::setOutputMode(const QSize &size) -{ - Command command = makeCommand(Impl::Compositor::setOutputMode, m_compositor); - command.parameters << size; - processCommand(command); -} - -void MockCompositor::setKeyboardFocus(const QSharedPointer<MockSurface> &surface) -{ - Command command = makeCommand(Impl::Compositor::setKeyboardFocus, m_compositor); - command.parameters << QVariant::fromValue(surface); - processCommand(command); -} - -void MockCompositor::sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos) -{ - Command command = makeCommand(Impl::Compositor::sendMousePress, m_compositor); - command.parameters << QVariant::fromValue(surface) << pos; - processCommand(command); -} - -void MockCompositor::sendMouseRelease(const QSharedPointer<MockSurface> &surface) -{ - Command command = makeCommand(Impl::Compositor::sendMouseRelease, m_compositor); - command.parameters << QVariant::fromValue(surface); - processCommand(command); -} - -void MockCompositor::sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code) -{ - Command command = makeCommand(Impl::Compositor::sendKeyPress, m_compositor); - command.parameters << QVariant::fromValue(surface) << code; - processCommand(command); -} - -void MockCompositor::sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code) -{ - Command command = makeCommand(Impl::Compositor::sendKeyRelease, m_compositor); - command.parameters << QVariant::fromValue(surface) << code; - processCommand(command); -} - -void MockCompositor::sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id) -{ - Command command = makeCommand(Impl::Compositor::sendTouchDown, m_compositor); - command.parameters << QVariant::fromValue(surface) << position << id; - processCommand(command); -} - -void MockCompositor::sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id) -{ - Command command = makeCommand(Impl::Compositor::sendTouchMotion, m_compositor); - command.parameters << QVariant::fromValue(surface) << position << id; - processCommand(command); -} - -void MockCompositor::sendTouchUp(const QSharedPointer<MockSurface> &surface, int id) -{ - Command command = makeCommand(Impl::Compositor::sendTouchUp, m_compositor); - command.parameters << QVariant::fromValue(surface) << id; - processCommand(command); -} - -void MockCompositor::sendTouchFrame(const QSharedPointer<MockSurface> &surface) -{ - Command command = makeCommand(Impl::Compositor::sendTouchFrame, m_compositor); - command.parameters << QVariant::fromValue(surface); - processCommand(command); -} - -void MockCompositor::sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface) -{ - Command command = makeCommand(Impl::Compositor::sendDataDeviceDataOffer, m_compositor); - command.parameters << QVariant::fromValue(surface); - processCommand(command); -} - -void MockCompositor::sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint& position) -{ - Command command = makeCommand(Impl::Compositor::sendDataDeviceEnter, m_compositor); - command.parameters << QVariant::fromValue(surface) << QVariant::fromValue(position); - processCommand(command); -} - -void MockCompositor::sendDataDeviceMotion(const QPoint &position) -{ - Command command = makeCommand(Impl::Compositor::sendDataDeviceMotion, m_compositor); - command.parameters << QVariant::fromValue(position); - processCommand(command); -} - -void MockCompositor::sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface) -{ - Command command = makeCommand(Impl::Compositor::sendDataDeviceDrop, m_compositor); - command.parameters << QVariant::fromValue(surface); - processCommand(command); -} - -void MockCompositor::sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface) -{ - Command command = makeCommand(Impl::Compositor::sendDataDeviceLeave, m_compositor); - command.parameters << QVariant::fromValue(surface); - processCommand(command); -} - -void MockCompositor::sendAddOutput() -{ - Command command = makeCommand(Impl::Compositor::sendAddOutput, m_compositor); - processCommand(command); -} - -void MockCompositor::sendRemoveOutput(const QSharedPointer<MockOutput> &output) -{ - Command command = makeCommand(Impl::Compositor::sendRemoveOutput, m_compositor); - command.parameters << QVariant::fromValue(output); - processCommand(command); -} - -void MockCompositor::sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry) -{ - Command command = makeCommand(Impl::Compositor::sendOutputGeometry, m_compositor); - command.parameters << QVariant::fromValue(output); - command.parameters << QVariant::fromValue(geometry); - processCommand(command); -} - -void MockCompositor::sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output) -{ - Command command = makeCommand(Impl::Compositor::sendSurfaceEnter, m_compositor); - command.parameters << QVariant::fromValue(surface); - command.parameters << QVariant::fromValue(output); - processCommand(command); -} - -void MockCompositor::sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output) -{ - Command command = makeCommand(Impl::Compositor::sendSurfaceLeave, m_compositor); - command.parameters << QVariant::fromValue(surface); - command.parameters << QVariant::fromValue(output); - processCommand(command); -} - -void MockCompositor::sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size) -{ - Command command = makeCommand(Impl::Compositor::sendShellSurfaceConfigure, m_compositor); - command.parameters << QVariant::fromValue(surface); - command.parameters << QVariant::fromValue(size); - processCommand(command); -} - -void MockCompositor::sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size) -{ - Command command = makeCommand(Impl::Compositor::sendIviSurfaceConfigure, m_compositor); - command.parameters << QVariant::fromValue(iviSurface); - command.parameters << QVariant::fromValue(size); - processCommand(command); -} - -void MockCompositor::sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size, const QVector<uint> &states) -{ - Command command = makeCommand(Impl::Compositor::sendXdgToplevelV6Configure, m_compositor); - command.parameters << QVariant::fromValue(toplevel); - command.parameters << QVariant::fromValue(size); - QByteArray statesBytes(reinterpret_cast<const char *>(states.data()), - states.size() * static_cast<int>(sizeof(uint))); - command.parameters << statesBytes; - processCommand(command); -} - -void MockCompositor::waitForStartDrag() -{ - Command command = makeCommand(Impl::Compositor::waitForStartDrag, m_compositor); - processCommand(command); -} - -QSharedPointer<MockSurface> MockCompositor::surface() -{ - QSharedPointer<MockSurface> result; - lock(); - QVector<Impl::Surface *> surfaces = m_compositor->surfaces(); - foreach (Impl::Surface *surface, surfaces) { - // we don't want to mistake the cursor surface for a window surface - if (surface->isMapped()) { - result = surface->mockSurface(); +namespace MockCompositor { + +DefaultCompositor::DefaultCompositor(CompositorType t, int socketFd) + : CoreCompositor(t, socketFd) +{ + { + Lock l(this); + + // Globals: Should ideally always be at least the latest versions we support. + // Legacy versions can override in separate tests by removing and adding. + add<WlCompositor>(); + add<SubCompositor>(); + auto *output = add<Output>(); + output->m_data.physicalSize = output->m_data.mode.physicalSizeForDpi(96); + add<Seat>(Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch); + add<WlShell>(); + add<XdgWmBase>(); + add<FractionalScaleManager>(); + add<Viewporter>(); + add<XdgWmDialog>(); + + switch (m_type) { + case CompositorType::Default: + add<Shm>(); + break; + case CompositorType::Legacy: + wl_display_init_shm(m_display); break; } + add<FullScreenShellV1>(); + add<IviApplication>(); + + // TODO: other shells, viewporter, xdgoutput etc + + QObject::connect(get<WlCompositor>(), &WlCompositor::surfaceCreated, [this] (Surface *surface){ + QObject::connect(surface, &Surface::bufferCommitted, [this, surface] { + if (m_config.autoRelease) { + // Pretend we made a copy of the buffer and just release it immediately + surface->m_committed.buffer->send_release(); + } + if (m_config.autoFrameCallback) { + surface->sendFrameCallbacks(); + } + if (m_config.autoEnter && get<Output>() && surface->m_outputs.empty()) + surface->sendEnter(get<Output>()); + wl_display_flush_clients(m_display); + }); + }); + + QObject::connect(get<XdgWmBase>(), &XdgWmBase::toplevelCreated, get<XdgWmBase>(), [this] (XdgToplevel *toplevel) { + if (m_config.autoConfigure) + toplevel->sendCompleteConfigure(); + }, Qt::DirectConnection); } - unlock(); - return result; -} - -QSharedPointer<MockOutput> MockCompositor::output(int index) -{ - QSharedPointer<MockOutput> result; - lock(); - if (Impl::Output *output = m_compositor->outputs().value(index, nullptr)) - result = output->mockOutput(); - unlock(); - return result; -} - -QSharedPointer<MockIviSurface> MockCompositor::iviSurface(int index) -{ - QSharedPointer<MockIviSurface> result; - lock(); - if (Impl::IviSurface *toplevel = m_compositor->iviApplication()->iviSurfaces().value(index, nullptr)) - result = toplevel->mockIviSurface(); - unlock(); - return result; -} - -QSharedPointer<MockXdgToplevelV6> MockCompositor::xdgToplevelV6(int index) -{ - QSharedPointer<MockXdgToplevelV6> result; - lock(); - if (Impl::XdgToplevelV6 *toplevel = m_compositor->xdgShellV6()->toplevels().value(index, nullptr)) - result = toplevel->mockToplevel(); - unlock(); - return result; -} - -MockCompositor::Command MockCompositor::makeCommand(Command::Callback callback, void *target) -{ - Command command; - command.callback = callback; - command.target = target; - return command; -} - -void MockCompositor::processCommand(const Command &command) -{ - lock(); - m_commandQueue << command; - unlock(); - - m_waitCondition.wakeOne(); -} - -void MockCompositor::dispatchCommands() -{ - lock(); - int count = m_commandQueue.length(); - unlock(); - - for (int i = 0; i < count; ++i) { - lock(); - const Command command = m_commandQueue.takeFirst(); - unlock(); - command.callback(command.target, command.parameters); - } -} - -void *MockCompositor::run(void *data) -{ - MockCompositor *controller = static_cast<MockCompositor *>(data); - - Impl::Compositor compositor; - - controller->m_compositor = &compositor; - controller->m_waitCondition.wakeOne(); - - while (!controller->m_ready) { - controller->dispatchCommands(); - compositor.dispatchEvents(20); - } - - while (controller->m_alive) { - { - QMutexLocker locker(&controller->m_mutex); - if (controller->m_commandQueue.isEmpty()) - controller->m_waitCondition.wait(&controller->m_mutex); + Q_ASSERT(isClean()); +} + +Surface *DefaultCompositor::surface(int i) +{ + QList<Surface *> surfaces; + switch (m_type) { + case CompositorType::Default: + return get<WlCompositor>()->m_surfaces.value(i, nullptr); + case CompositorType::Legacy: { + QList<Surface *> msurfaces = get<WlCompositor>()->m_surfaces; + for (Surface *surface : msurfaces) { + if (surface->isMapped()) { + surfaces << surface; + } + } } - controller->dispatchCommands(); - compositor.dispatchEvents(20); + break; } - return 0; -} - -namespace Impl { - -Compositor::Compositor() - : m_display(wl_display_create()) -{ - if (wl_display_add_socket(m_display, 0)) { - fprintf(stderr, "Fatal: Failed to open server socket\n"); - exit(EXIT_FAILURE); - } - - wl_global_create(m_display, &wl_compositor_interface, 1, this, bindCompositor); - - m_data_device_manager.reset(new DataDeviceManager(this, m_display)); - - wl_display_init_shm(m_display); - - m_seat.reset(new Seat(this, m_display)); - m_pointer = m_seat->pointer(); - m_keyboard = m_seat->keyboard(); - m_touch = m_seat->touch(); + if (i >= 0 && i < surfaces.size()) + return surfaces[i]; - m_outputs.append(new Output(m_display, QSize(1920, 1080), QPoint(0, 0))); - m_iviApplication.reset(new IviApplication(m_display)); - m_wlShell.reset(new WlShell(m_display)); - m_xdgShellV6.reset(new XdgShellV6(m_display)); - - m_loop = wl_display_get_event_loop(m_display); - m_fd = wl_event_loop_get_fd(m_loop); -} - -Compositor::~Compositor() -{ - wl_display_destroy(m_display); -} - -void Compositor::dispatchEvents(int timeout) -{ - wl_display_flush_clients(m_display); - wl_event_loop_dispatch(m_loop, timeout); -} - -static void compositor_create_surface(wl_client *client, wl_resource *compositorResource, uint32_t id) -{ - Compositor *compositor = static_cast<Compositor *>(wl_resource_get_user_data(compositorResource)); - compositor->addSurface(new Surface(client, id, wl_resource_get_version(compositorResource), compositor)); + return nullptr; } -static void compositor_create_region(wl_client *client, wl_resource *compositorResource, uint32_t id) +uint DefaultCompositor::sendXdgShellPing() { - Q_UNUSED(client); - Q_UNUSED(compositorResource); - Q_UNUSED(id); + warnIfNotLockedByThread(Q_FUNC_INFO); + uint serial = nextSerial(); + auto *base = get<XdgWmBase>(); + const auto resourceMap = base->resourceMap(); + Q_ASSERT(resourceMap.size() == 1); // binding more than once shouldn't be needed + base->send_ping(resourceMap.first()->handle, serial); + return serial; } -void Compositor::bindCompositor(wl_client *client, void *compositorData, uint32_t version, uint32_t id) +void DefaultCompositor::xdgPingAndWaitForPong() { - static const struct wl_compositor_interface compositorInterface = { - compositor_create_surface, - compositor_create_region - }; - - wl_resource *resource = wl_resource_create(client, &wl_compositor_interface, static_cast<int>(version), id); - wl_resource_set_implementation(resource, &compositorInterface, compositorData, nullptr); + QSignalSpy pongSpy(exec([&] { return get<XdgWmBase>(); }), &XdgWmBase::pong); + uint serial = exec([&] { return sendXdgShellPing(); }); + QTRY_COMPARE(pongSpy.size(), 1); + QTRY_COMPARE(pongSpy.first().at(0).toUInt(), serial); } -static void unregisterResourceCallback(wl_listener *listener, void *data) +void DefaultCompositor::sendShellSurfaceConfigure(Surface *surface) { - struct wl_resource *resource = reinterpret_cast<struct wl_resource *>(data); - wl_list_remove(wl_resource_get_link(resource)); - delete listener; -} - -void registerResource(wl_list *list, wl_resource *resource) -{ - wl_list_insert(list, wl_resource_get_link(resource)); - - wl_listener *listener = new wl_listener; - listener->notify = unregisterResourceCallback; - - wl_resource_add_destroy_listener(resource, listener); -} - -QVector<Surface *> Compositor::surfaces() const -{ - return m_surfaces; -} - -QVector<Output *> Compositor::outputs() const -{ - return m_outputs; -} - -IviApplication *Compositor::iviApplication() const -{ - return m_iviApplication.data(); -} - -XdgShellV6 *Compositor::xdgShellV6() const -{ - return m_xdgShellV6.data(); -} - -uint32_t Compositor::nextSerial() -{ - return wl_display_next_serial(m_display); -} - -void Compositor::addSurface(Surface *surface) -{ - m_surfaces << surface; -} + switch (m_type) { + case CompositorType::Default: + break; + case CompositorType::Legacy: { + if (auto wlShellSurface = surface->wlShellSurface()) { + wlShellSurface->sendConfigure(0, 0, 0); + return; + } + break; + } + } -void Compositor::removeSurface(Surface *surface) -{ - m_surfaces.removeOne(surface); - m_keyboard->handleSurfaceDestroyed(surface); - m_pointer->handleSurfaceDestroyed(surface); + qWarning() << "The mocking framework doesn't know how to send a configure event for this surface"; } -Surface *Compositor::resolveSurface(const QVariant &v) +WlShellCompositor::WlShellCompositor(CompositorType t) + : DefaultCompositor(t) { - QSharedPointer<MockSurface> mockSurface = v.value<QSharedPointer<MockSurface> >(); - return mockSurface ? mockSurface->handle() : nullptr; } -Output *Compositor::resolveOutput(const QVariant &v) +Surface *DefaultCompositor::wlSurface(int i) { - QSharedPointer<MockOutput> mockOutput = v.value<QSharedPointer<MockOutput> >(); - return mockOutput ? mockOutput->handle() : nullptr; -} + QList<Surface *> surfaces, msurfaces; + msurfaces = get<WlCompositor>()->m_surfaces; + for (Surface *surface : msurfaces) { + if (surface->isMapped()) + surfaces << surface; + } -IviSurface *Compositor::resolveIviSurface(const QVariant &v) -{ - QSharedPointer<MockIviSurface> mockIviSurface = v.value<QSharedPointer<MockIviSurface>>(); - return mockIviSurface ? mockIviSurface->handle() : nullptr; -} + if (i >=0 && i < surfaces.size()) + return surfaces[i]; -XdgToplevelV6 *Compositor::resolveToplevel(const QVariant &v) -{ - QSharedPointer<MockXdgToplevelV6> mockToplevel = v.value<QSharedPointer<MockXdgToplevelV6>>(); - return mockToplevel ? mockToplevel->handle() : nullptr; + return nullptr; } -} +} // namespace MockCompositor diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h index 51b6f4bfb..9a2c06a17 100644 --- a/tests/auto/client/shared/mockcompositor.h +++ b/tests/auto/client/shared/mockcompositor.h @@ -1,287 +1,97 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2021 David Edmundson <davidedmundson@kde.org> +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef MOCKCOMPOSITOR_H #define MOCKCOMPOSITOR_H -#include "mockxdgshellv6.h" -#include "mockiviapplication.h" - -#include <pthread.h> -#include <qglobal.h> -#include <wayland-server.h> - -#include <QImage> -#include <QMutex> -#include <QRect> -#include <QSharedPointer> -#include <QVariant> -#include <QVector> -#include <QWaitCondition> - -namespace Impl { - -typedef void (**Implementation)(void); - -class Keyboard; -class Pointer; -class Touch; -class Seat; -class DataDeviceManager; -class Surface; -class Output; -class IviApplication; -class WlShell; -class XdgShellV6; - -class Compositor -{ -public: - Compositor(); - ~Compositor(); - - int fileDescriptor() const { return m_fd; } - void dispatchEvents(int timeout = 0); - - uint32_t nextSerial(); - uint32_t time() { return ++m_time; } - - QVector<Surface *> surfaces() const; - QVector<Output *> outputs() const; - - IviApplication *iviApplication() const; - XdgShellV6 *xdgShellV6() const; - - void addSurface(Surface *surface); - void removeSurface(Surface *surface); - - static void setKeyboardFocus(void *data, const QList<QVariant> ¶meters); - static void sendMousePress(void *data, const QList<QVariant> ¶meters); - static void sendMouseRelease(void *data, const QList<QVariant> ¶meters); - static void sendKeyPress(void *data, const QList<QVariant> ¶meters); - static void sendKeyRelease(void *data, const QList<QVariant> ¶meters); - static void sendTouchDown(void *data, const QList<QVariant> ¶meters); - static void sendTouchUp(void *data, const QList<QVariant> ¶meters); - static void sendTouchMotion(void *data, const QList<QVariant> ¶meters); - static void sendTouchFrame(void *data, const QList<QVariant> ¶meters); - static void sendDataDeviceDataOffer(void *data, const QList<QVariant> ¶meters); - static void sendDataDeviceEnter(void *data, const QList<QVariant> ¶meters); - static void sendDataDeviceMotion(void *data, const QList<QVariant> ¶meters); - static void sendDataDeviceDrop(void *data, const QList<QVariant> ¶meters); - static void sendDataDeviceLeave(void *data, const QList<QVariant> ¶meters); - static void waitForStartDrag(void *data, const QList<QVariant> ¶meters); - static void setOutputMode(void *compositor, const QList<QVariant> ¶meters); - static void sendAddOutput(void *data, const QList<QVariant> ¶meters); - static void sendRemoveOutput(void *data, const QList<QVariant> ¶meters); - static void sendOutputGeometry(void *data, const QList<QVariant> ¶meters); - static void sendSurfaceEnter(void *data, const QList<QVariant> ¶meters); - static void sendSurfaceLeave(void *data, const QList<QVariant> ¶meters); - static void sendShellSurfaceConfigure(void *data, const QList<QVariant> ¶meters); - static void sendIviSurfaceConfigure(void *data, const QList<QVariant> ¶meters); - static void sendXdgToplevelV6Configure(void *data, const QList<QVariant> ¶meters); - -public: - bool m_startDragSeen = false; - -private: - static void bindCompositor(wl_client *client, void *data, uint32_t version, uint32_t id); - static Surface *resolveSurface(const QVariant &v); - static Output *resolveOutput(const QVariant &v); - static IviSurface *resolveIviSurface(const QVariant &v); - static XdgToplevelV6 *resolveToplevel(const QVariant &v); - - void initShm(); - - QRect m_outputGeometry; - - wl_display *m_display = nullptr; - wl_event_loop *m_loop = nullptr; - wl_shm *m_shm = nullptr; - int m_fd = -1; - - uint32_t m_time = 0; - - QScopedPointer<Seat> m_seat; - Pointer *m_pointer = nullptr; - Keyboard *m_keyboard = nullptr; - Touch *m_touch = nullptr; - QScopedPointer<DataDeviceManager> m_data_device_manager; - QVector<Surface *> m_surfaces; - QVector<Output *> m_outputs; - QScopedPointer<IviApplication> m_iviApplication; - QScopedPointer<WlShell> m_wlShell; - QScopedPointer<XdgShellV6> m_xdgShellV6; -}; - -void registerResource(wl_list *list, wl_resource *resource); - -} - -class MockSurface -{ -public: - Impl::Surface *handle() const { return m_surface; } - - QImage image; - -private: - MockSurface(Impl::Surface *surface); - friend class Impl::Compositor; - friend class Impl::Surface; - - Impl::Surface *m_surface = nullptr; -}; +#include "corecompositor.h" +#include "coreprotocol.h" +#include "datadevice.h" +#include "fullscreenshellv1.h" +#include "iviapplication.h" +#include "xdgshell.h" +#include "viewport.h" +#include "fractionalscalev1.h" +#include "xdgdialog.h" + +#include <QtGui/QGuiApplication> + +// 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 -Q_DECLARE_METATYPE(QSharedPointer<MockSurface>) +namespace MockCompositor { -class MockIviSurface +class DefaultCompositor : public CoreCompositor { public: - Impl::IviSurface *handle() const { return m_iviSurface; } - const uint iviId; - -private: - MockIviSurface(Impl::IviSurface *iviSurface) : iviId(iviSurface->iviId()), m_iviSurface(iviSurface) {} - friend class Impl::Compositor; - friend class Impl::IviSurface; - - Impl::IviSurface *m_iviSurface; + explicit DefaultCompositor(CompositorType t = CompositorType::Default, int socketFd = -1); + // Convenience functions + Output *output(int i = 0) { return getAll<Output>().value(i, nullptr); } + Surface *surface(int i = 0); + Subsurface *subSurface(int i = 0) { return get<SubCompositor>()->m_subsurfaces.value(i, nullptr); } + WlShellSurface *wlShellSurface(int i = 0) { return get<WlShell>()->m_wlShellSurfaces.value(i, nullptr); } + Surface *wlSurface(int i = 0); + XdgSurface *xdgSurface(int i = 0) { return get<XdgWmBase>()->m_xdgSurfaces.value(i, nullptr); } + XdgToplevel *xdgToplevel(int i = 0) { return get<XdgWmBase>()->toplevel(i); } + XdgPopup *xdgPopup(int i = 0) { return get<XdgWmBase>()->popup(i); } + Pointer *pointer() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_pointer; } + Touch *touch() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_touch; } + Surface *cursorSurface() { auto *p = pointer(); return p ? p->cursorSurface() : nullptr; } + Keyboard *keyboard() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_keyboard; } + FullScreenShellV1 *fullScreenShellV1() {return get<FullScreenShellV1>();}; + IviSurface *iviSurface(int i = 0) { return get<IviApplication>()->m_iviSurfaces.value(i, nullptr); } + FractionalScale *fractionalScale(int i = 0) {return get<FractionalScaleManager>()->m_fractionalScales.value(i, nullptr); } + Viewport *viewport(int i = 0) {return get<Viewporter>()->m_viewports.value(i, nullptr); } + XdgDialog *xdgDialog(int i = 0) { return get<XdgWmDialog>()->m_dialogs.value(i, nullptr); } + + uint sendXdgShellPing(); + void xdgPingAndWaitForPong(); + + void sendShellSurfaceConfigure(Surface *surface); + + // Things that can be changed run-time without confusing the client (i.e. don't require separate tests) + struct Config { + bool autoEnter = true; + bool autoRelease = true; + bool autoConfigure = false; + bool autoFrameCallback = true; + } m_config; + void resetConfig() { exec([&] { m_config = Config{}; }); } }; -Q_DECLARE_METATYPE(QSharedPointer<MockIviSurface>) - -class MockXdgToplevelV6 : public QObject +class WlShellCompositor : public DefaultCompositor { - Q_OBJECT -public: - Impl::XdgToplevelV6 *handle() const { return m_toplevel; } - - void sendConfigure(const QSharedPointer<MockXdgToplevelV6> toplevel); - -signals: - uint setMinimizedRequested(); - uint setMaximizedRequested(); - uint unsetMaximizedRequested(); - uint setFullscreenRequested(); - uint unsetFullscreenRequested(); - void windowGeometryRequested(QRect geometry); // NOTE: This is really an xdg surface event - -private: - MockXdgToplevelV6(Impl::XdgToplevelV6 *toplevel) : m_toplevel(toplevel) {} - friend class Impl::Compositor; - friend class Impl::XdgToplevelV6; - - Impl::XdgToplevelV6 *m_toplevel; -}; - -Q_DECLARE_METATYPE(QSharedPointer<MockXdgToplevelV6>) - -class MockOutput { public: - Impl::Output *handle() const { return m_output; } - MockOutput(Impl::Output *output); -private: - Impl::Output *m_output = nullptr; + explicit WlShellCompositor(CompositorType t = CompositorType::Legacy); }; -Q_DECLARE_METATYPE(QSharedPointer<MockOutput>) - -class MockCompositor -{ -public: - MockCompositor(); - ~MockCompositor(); - - void applicationInitialized(); - - int waylandFileDescriptor() const; - void processWaylandEvents(); - - void setOutputMode(const QSize &size); - void setKeyboardFocus(const QSharedPointer<MockSurface> &surface); - void sendMousePress(const QSharedPointer<MockSurface> &surface, const QPoint &pos); - void sendMouseRelease(const QSharedPointer<MockSurface> &surface); - void sendKeyPress(const QSharedPointer<MockSurface> &surface, uint code); - void sendKeyRelease(const QSharedPointer<MockSurface> &surface, uint code); - void sendTouchDown(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id); - void sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id); - void sendTouchUp(const QSharedPointer<MockSurface> &surface, int id); - void sendTouchFrame(const QSharedPointer<MockSurface> &surface); - void sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface); - void sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint &position); - void sendDataDeviceMotion(const QPoint &position); - void sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface); - void sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface); - void sendAddOutput(); - void sendRemoveOutput(const QSharedPointer<MockOutput> &output); - void sendOutputGeometry(const QSharedPointer<MockOutput> &output, const QRect &geometry); - void sendSurfaceEnter(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output); - void sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output); - void sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size = QSize(0, 0)); - void sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size); - void sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size = QSize(0, 0), - const QVector<uint> &states = { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED }); - void waitForStartDrag(); - - QSharedPointer<MockSurface> surface(); - QSharedPointer<MockOutput> output(int index = 0); - QSharedPointer<MockIviSurface> iviSurface(int index = 0); - QSharedPointer<MockXdgToplevelV6> xdgToplevelV6(int index = 0); - - void lock(); - void unlock(); - -private: - struct Command - { - typedef void (*Callback)(void *target, const QList<QVariant> ¶meters); - - Callback callback; - void *target = nullptr; - QList<QVariant> parameters; - }; - - static Command makeCommand(Command::Callback callback, void *target); - - void processCommand(const Command &command); - void dispatchCommands(); - - static void *run(void *data); - - bool m_alive = true; - bool m_ready = false; - pthread_t m_thread; - QMutex m_mutex; - QWaitCondition m_waitCondition; - - Impl::Compositor *m_compositor = nullptr; - - QList<Command> m_commandQueue; -}; +} // namespace MockCompositor + +#define QCOMPOSITOR_VERIFY(expr) QVERIFY(exec([&]{ return expr; })) +#define QCOMPOSITOR_TRY_VERIFY(expr) QTRY_VERIFY(exec([&]{ return expr; })) +#define QCOMPOSITOR_COMPARE(expr, expr2) QCOMPARE(exec([&]{ return expr; }), expr2) +#define QCOMPOSITOR_TRY_COMPARE(expr, expr2) QTRY_COMPARE(exec([&]{ return expr; }), expr2) + +#define QCOMPOSITOR_TEST_MAIN(test) \ +int main(int argc, char **argv) \ +{ \ + QTemporaryDir tmpRuntimeDir; \ + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); \ + setenv("XDG_CURRENT_DESKTOP", "qtwaylandtests", 1); \ + setenv("QT_QPA_PLATFORM", "wayland", 1); \ + test tc; \ + QGuiApplication app(argc, argv); \ + QTEST_SET_MAIN_SOURCE_PATH \ + return QTest::qExec(&tc, argc, argv); \ +} \ #endif diff --git a/tests/auto/client/shared/mockinput.cpp b/tests/auto/client/shared/mockinput.cpp deleted file mode 100644 index 8b7592824..000000000 --- a/tests/auto/client/shared/mockinput.cpp +++ /dev/null @@ -1,474 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "mockinput.h" -#include "mocksurface.h" - -namespace Impl { - -void Compositor::setKeyboardFocus(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - compositor->m_keyboard->setFocus(resolveSurface(parameters.first())); -} - -void Compositor::sendMousePress(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - if (!surface) - return; - - QPoint pos = parameters.last().toPoint(); - compositor->m_pointer->setFocus(surface, pos); - compositor->m_pointer->sendMotion(pos); - compositor->m_pointer->sendButton(0x110, 1); -} - -void Compositor::sendMouseRelease(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - if (!surface) - return; - - compositor->m_pointer->sendButton(0x110, 0); -} - -void Compositor::sendKeyPress(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - if (!surface) - return; - - compositor->m_keyboard->sendKey(parameters.last().toUInt() - 8, 1); -} - -void Compositor::sendKeyRelease(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - if (!surface) - return; - - compositor->m_keyboard->sendKey(parameters.last().toUInt() - 8, 0); -} - -void Compositor::sendTouchDown(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - QPoint position = parameters.at(1).toPoint(); - int id = parameters.at(2).toInt(); - - compositor->m_touch->sendDown(surface, position, id); -} - -void Compositor::sendTouchUp(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - int id = parameters.at(1).toInt(); - - compositor->m_touch->sendUp(surface, id); -} - -void Compositor::sendTouchMotion(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - QPoint position = parameters.at(1).toPoint(); - int id = parameters.at(2).toInt(); - - compositor->m_touch->sendMotion(surface, position, id); -} - -void Compositor::sendTouchFrame(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - compositor->m_touch->sendFrame(surface); -} - -void Compositor::sendDataDeviceDataOffer(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - compositor->m_data_device_manager->dataDevice()->sendDataOffer(surface->resource()->client()); -} - -void Compositor::sendDataDeviceEnter(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - QPoint position = parameters.at(1).toPoint(); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - compositor->m_data_device_manager->dataDevice()->sendEnter(surface, position); -} - -void Compositor::sendDataDeviceMotion(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Q_ASSERT(compositor); - QPoint position = parameters.first().toPoint(); - compositor->m_data_device_manager->dataDevice()->sendMotion(position); -} - -void Compositor::sendDataDeviceDrop(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - compositor->m_data_device_manager->dataDevice()->sendDrop(surface); -} - -void Compositor::sendDataDeviceLeave(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.first()); - - Q_ASSERT(compositor); - Q_ASSERT(surface); - - compositor->m_data_device_manager->dataDevice()->sendLeave(surface); -} - -void Compositor::waitForStartDrag(void *data, const QList<QVariant> ¶meters) -{ - Q_UNUSED(parameters); - Compositor *compositor = static_cast<Compositor *>(data); - Q_ASSERT(compositor); - while (!compositor->m_startDragSeen) { - wl_display_flush_clients(compositor->m_display); - wl_event_loop_dispatch(compositor->m_loop, 100); - } - compositor->m_startDragSeen = false; -} - -Seat::Seat(Compositor *compositor, struct ::wl_display *display) - : wl_seat(display, 2) - , m_compositor(compositor) - , m_keyboard(new Keyboard(compositor)) - , m_pointer(new Pointer(compositor)) - , m_touch(new Touch(compositor)) -{ -} - -Seat::~Seat() -{ -} - -void Seat::seat_bind_resource(Resource *resource) -{ - send_capabilities(resource->handle, capability_keyboard | capability_pointer | capability_touch); -} - -void Seat::seat_get_keyboard(Resource *resource, uint32_t id) -{ - m_keyboard->add(resource->client(), id, resource->version()); -} - -void Seat::seat_get_pointer(Resource *resource, uint32_t id) -{ - m_pointer->add(resource->client(), id, resource->version()); -} - -void Seat::seat_get_touch(Resource *resource, uint32_t id) -{ - m_touch->add(resource->client(), id, resource->version()); -} - -Keyboard::Keyboard(Compositor *compositor) - : m_compositor(compositor) -{ -} - -Keyboard::~Keyboard() -{ -} - -void Keyboard::setFocus(Surface *surface) -{ - if (m_focusResource && m_focus != surface) { - uint32_t serial = m_compositor->nextSerial(); - send_leave(m_focusResource->handle, serial, m_focus->resource()->handle); - } - - Resource *resource = surface ? resourceMap().value(surface->resource()->client()) : 0; - - if (resource && (m_focus != surface || m_focusResource != resource)) { - uint32_t serial = m_compositor->nextSerial(); - send_modifiers(resource->handle, serial, 0, 0, 0, 0); - send_enter(resource->handle, serial, surface->resource()->handle, QByteArray()); - } - - m_focusResource = resource; - m_focus = surface; -} - -void Keyboard::handleSurfaceDestroyed(Surface *surface) -{ - if (surface == m_focus) { - m_focusResource = nullptr; - m_focus = nullptr; - } -} - -void Keyboard::sendKey(uint32_t key, uint32_t state) -{ - if (m_focusResource) { - uint32_t serial = m_compositor->nextSerial(); - send_key(m_focusResource->handle, serial, m_compositor->time(), key, state); - } -} - - -void Keyboard::keyboard_destroy_resource(wl_keyboard::Resource *resource) -{ - if (m_focusResource == resource) - m_focusResource = 0; -} - -Pointer::Pointer(Compositor *compositor) - : m_compositor(compositor) -{ -} - -Pointer::~Pointer() -{ - -} - -void Pointer::setFocus(Surface *surface, const QPoint &pos) -{ - if (m_focusResource && m_focus != surface) { - uint32_t serial = m_compositor->nextSerial(); - send_leave(m_focusResource->handle, serial, m_focus->resource()->handle); - } - - Resource *resource = surface ? resourceMap().value(surface->resource()->client()) : 0; - - if (resource && (m_focus != surface || resource != m_focusResource)) { - uint32_t serial = m_compositor->nextSerial(); - send_enter(resource->handle, serial, surface->resource()->handle, - wl_fixed_from_int(pos.x()), wl_fixed_from_int(pos.y())); - } - - m_focusResource = resource; - m_focus = surface; -} - -void Pointer::handleSurfaceDestroyed(Surface *surface) -{ - if (m_focus == surface) { - m_focus = nullptr; - m_focusResource = nullptr; - } -} - -void Pointer::sendMotion(const QPoint &pos) -{ - if (m_focusResource) - send_motion(m_focusResource->handle, m_compositor->time(), - wl_fixed_from_int(pos.x()), wl_fixed_from_int(pos.y())); -} - -void Pointer::sendButton(uint32_t button, uint32_t state) -{ - if (m_focusResource) { - uint32_t serial = m_compositor->nextSerial(); - send_button(m_focusResource->handle, serial, m_compositor->time(), - button, state); - } -} - -void Pointer::pointer_destroy_resource(wl_pointer::Resource *resource) -{ - if (m_focusResource == resource) - m_focusResource = 0; -} - -Touch::Touch(Compositor *compositor) - : wl_touch() - , m_compositor(compositor) -{ -} - -void Touch::sendDown(Surface *surface, const QPoint &position, int id) -{ - uint32_t serial = m_compositor->nextSerial(); - uint32_t time = m_compositor->time(); - Q_ASSERT(surface); - Resource *resource = resourceMap().value(surface->resource()->client()); - Q_ASSERT(resource); - auto x = wl_fixed_from_int(position.x()); - auto y = wl_fixed_from_int(position.y()); - wl_touch_send_down(resource->handle, serial, time, surface->resource()->handle, id, x, y); -} - -void Touch::sendUp(Surface *surface, int id) -{ - Resource *resource = resourceMap().value(surface->resource()->client()); - wl_touch_send_up(resource->handle, m_compositor->nextSerial(), m_compositor->time(), id); -} - -void Touch::sendMotion(Surface *surface, const QPoint &position, int id) -{ - Resource *resource = resourceMap().value(surface->resource()->client()); - uint32_t time = m_compositor->time(); - auto x = wl_fixed_from_int(position.x()); - auto y = wl_fixed_from_int(position.y()); - wl_touch_send_motion(resource->handle, time, id, x, y); -} - -void Touch::sendFrame(Surface *surface) -{ - Resource *resource = resourceMap().value(surface->resource()->client()); - wl_touch_send_frame(resource->handle); -} - -DataOffer::DataOffer() - : wl_data_offer() -{ - -} - -DataDevice::DataDevice(Compositor *compositor) - : m_compositor(compositor) -{ - -} - -void DataDevice::sendDataOffer(wl_client *client) -{ - m_dataOffer = new QtWaylandServer::wl_data_offer(client, 0, 1); - Resource *resource = resourceMap().value(client); - send_data_offer(resource->handle, m_dataOffer->resource()->handle); -} - -void DataDevice::sendEnter(Surface *surface, const QPoint& position) -{ - uint serial = m_compositor->nextSerial(); - m_focus = surface; - Resource *resource = resourceMap().value(surface->resource()->client()); - send_enter(resource->handle, serial, surface->resource()->handle, position.x(), position.y(), m_dataOffer->resource()->handle); -} - -void DataDevice::sendMotion(const QPoint &position) -{ - uint32_t time = m_compositor->time(); - Resource *resource = resourceMap().value(m_focus->resource()->client()); - send_motion(resource->handle, time, position.x(), position.y()); -} - -void DataDevice::sendDrop(Surface *surface) -{ - Resource *resource = resourceMap().value(surface->resource()->client()); - send_drop(resource->handle); -} - -void DataDevice::sendLeave(Surface *surface) -{ - Resource *resource = resourceMap().value(surface->resource()->client()); - send_leave(resource->handle); -} - -DataDevice::~DataDevice() -{ - -} - -void DataDevice::data_device_start_drag(QtWaylandServer::wl_data_device::Resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial) -{ - Q_UNUSED(resource); - Q_UNUSED(source); - Q_UNUSED(origin); - Q_UNUSED(icon); - Q_UNUSED(serial); - m_compositor->m_startDragSeen = true; -} - -DataDeviceManager::DataDeviceManager(Compositor *compositor, wl_display *display) - : wl_data_device_manager(display, 1) - , m_compositor(compositor) -{ - -} - -DataDeviceManager::~DataDeviceManager() -{ - -} - -DataDevice *DataDeviceManager::dataDevice() const -{ - return m_data_device.data(); -} - -void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat) -{ - Q_UNUSED(seat); - if (!m_data_device) - m_data_device.reset(new DataDevice(m_compositor)); - m_data_device->add(resource->client(), id, 1); -} - -void DataDeviceManager::data_device_manager_create_data_source(QtWaylandServer::wl_data_device_manager::Resource *resource, uint32_t id) -{ - new QtWaylandServer::wl_data_source(resource->client(), id, 1); -} - -} diff --git a/tests/auto/client/shared/mockinput.h b/tests/auto/client/shared/mockinput.h deleted file mode 100644 index d9adb3621..000000000 --- a/tests/auto/client/shared/mockinput.h +++ /dev/null @@ -1,172 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Klarälvdalens Datakonsult AB (KDAB). -** 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$ -** -****************************************************************************/ - -#ifndef MOCKINPUT_H -#define MOCKINPUT_H - -#include <qglobal.h> - -#include "qwayland-server-wayland.h" - -#include "mockcompositor.h" - -namespace Impl { - -class Keyboard; -class Pointer; - -class Seat : public QtWaylandServer::wl_seat -{ -public: - Seat(Compositor *compositor, struct ::wl_display *display); - ~Seat(); - - Compositor *compositor() const { return m_compositor; } - - Keyboard *keyboard() const { return m_keyboard.data(); } - Pointer *pointer() const { return m_pointer.data(); } - Touch *touch() const { return m_touch.data(); } - -protected: - void seat_bind_resource(Resource *resource) override; - void seat_get_keyboard(Resource *resource, uint32_t id) override; - void seat_get_pointer(Resource *resource, uint32_t id) override; - void seat_get_touch(Resource *resource, uint32_t id) override; - -private: - Compositor *m_compositor = nullptr; - - QScopedPointer<Keyboard> m_keyboard; - QScopedPointer<Pointer> m_pointer; - QScopedPointer<Touch> m_touch; -}; - -class Keyboard : public QtWaylandServer::wl_keyboard -{ -public: - Keyboard(Compositor *compositor); - ~Keyboard(); - - Surface *focus() const { return m_focus; } - void setFocus(Surface *surface); - void handleSurfaceDestroyed(Surface *surface); - - void sendKey(uint32_t key, uint32_t state); - -protected: - void keyboard_destroy_resource(wl_keyboard::Resource *resource) override; - -private: - Compositor *m_compositor = nullptr; - - Resource *m_focusResource = nullptr; - Surface *m_focus = nullptr; -}; - -class Pointer : public QtWaylandServer::wl_pointer -{ -public: - Pointer(Compositor *compositor); - ~Pointer(); - - Surface *focus() const { return m_focus; } - - void setFocus(Surface *surface, const QPoint &pos); - void handleSurfaceDestroyed(Surface *surface); - void sendMotion(const QPoint &pos); - void sendButton(uint32_t button, uint32_t state); - -protected: - void pointer_destroy_resource(wl_pointer::Resource *resource) override; - -private: - Compositor *m_compositor = nullptr; - - Resource *m_focusResource = nullptr; - Surface *m_focus = nullptr; -}; - -class Touch : public QtWaylandServer::wl_touch -{ -public: - Touch(Compositor *compositor); - void sendDown(Surface *surface, const QPoint &position, int id); - void sendUp(Surface *surface, int id); - void sendMotion(Surface *surface, const QPoint &position, int id); - void sendFrame(Surface *surface); -private: - Compositor *m_compositor = nullptr; -}; - -class DataOffer : public QtWaylandServer::wl_data_offer -{ -public: - DataOffer(); -}; - -class DataDevice : public QtWaylandServer::wl_data_device -{ -public: - DataDevice(Compositor *compositor); - void sendDataOffer(wl_client *client); - void sendEnter(Surface *surface, const QPoint &position); - void sendMotion(const QPoint &position); - void sendDrop(Surface *surface); - void sendLeave(Surface *surface); - ~DataDevice(); - -protected: - void data_device_start_drag(Resource *resource, struct ::wl_resource *source, struct ::wl_resource *origin, struct ::wl_resource *icon, uint32_t serial) override; - -private: - Compositor *m_compositor = nullptr; - QtWaylandServer::wl_data_offer *m_dataOffer = nullptr; - Surface* m_focus = nullptr; -}; - -class DataDeviceManager : public QtWaylandServer::wl_data_device_manager -{ -public: - DataDeviceManager(Compositor *compositor, struct ::wl_display *display); - ~DataDeviceManager(); - DataDevice *dataDevice() const; - -protected: - void data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat) override; - void data_device_manager_create_data_source(Resource *resource, uint32_t id) override; - -private: - Compositor *m_compositor = nullptr; - - QScopedPointer<DataDevice> m_data_device; -}; - -} - -#endif // MOCKINPUT_H diff --git a/tests/auto/client/shared/mockiviapplication.cpp b/tests/auto/client/shared/mockiviapplication.cpp deleted file mode 100644 index 29a308993..000000000 --- a/tests/auto/client/shared/mockiviapplication.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 "mockiviapplication.h" -#include "mocksurface.h" -#include "mockcompositor.h" - -namespace Impl { - -void Compositor::sendIviSurfaceConfigure(void *data, const QList<QVariant> ¶meters) -{ - Q_UNUSED(data); - IviSurface *iviSurface = resolveIviSurface(parameters.at(0)); - Q_ASSERT(iviSurface && iviSurface->resource()); - QSize size = parameters.at(1).toSize(); - Q_ASSERT(!size.isEmpty()); - iviSurface->send_configure(size.width(), size.height()); -} - -IviSurface::IviSurface(IviApplication *iviApplication, Surface *surface, uint iviId, wl_client *client, uint32_t id) - : QtWaylandServer::ivi_surface(client, id, 1) - , m_surface(surface) - , m_iviApplication(iviApplication) - , m_iviId(iviId) - , m_mockIviSurface(new MockIviSurface(this)) -{ - iviApplication->addIviSurface(this); - surface->map(); -} - -IviSurface::~IviSurface() -{ - m_iviApplication->removeIviSurface(this); - m_mockIviSurface->m_iviSurface = nullptr; -} - -void IviSurface::ivi_surface_destroy(Resource *resource) -{ - wl_resource_destroy(resource->handle); -} - -void IviApplication::ivi_application_surface_create(Resource *resource, uint32_t ivi_id, ::wl_resource *surface, uint32_t id) -{ - new IviSurface(this, Surface::fromResource(surface), ivi_id, resource->client(), id); -} - -} // namespace Impl diff --git a/tests/auto/client/shared/mockiviapplication.h b/tests/auto/client/shared/mockiviapplication.h deleted file mode 100644 index 4d65eeaba..000000000 --- a/tests/auto/client/shared/mockiviapplication.h +++ /dev/null @@ -1,85 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef MOCKIVIAPPLICATION_H -#define MOCKIVIAPPLICATION_H - -#include <qwayland-server-ivi-application.h> - -#include <QSharedPointer> -#include <QVector> - -class MockIviSurface; - -namespace Impl { - -class Surface; -class IviApplication; - -class IviSurface : public QtWaylandServer::ivi_surface -{ -public: - IviSurface(IviApplication *iviApplication, Surface *surface, uint iviId, wl_client *client, uint32_t id); - ~IviSurface() override; - IviApplication *iviApplication() const { return m_iviApplication; } - Surface *surface() const { return m_surface; } - uint iviId() const { return m_iviId; } - - QSharedPointer<MockIviSurface> mockIviSurface() const { return m_mockIviSurface; } - -protected: - void ivi_surface_destroy_resource(Resource *) override { delete this; } - void ivi_surface_destroy(Resource *resource) override; - -private: - Surface *m_surface = nullptr; - IviApplication *m_iviApplication = nullptr; - const uint m_iviId = 0; - QSharedPointer<MockIviSurface> m_mockIviSurface; -}; - -class IviApplication : public QtWaylandServer::ivi_application -{ -public: - explicit IviApplication(::wl_display *display) : ivi_application(display, 1) {} - QVector<IviSurface *> iviSurfaces() const { return m_iviSurfaces; } - -protected: - void ivi_application_surface_create(Resource *resource, uint32_t ivi_id, ::wl_resource *surface, uint32_t id) override; - -private: - void addIviSurface(IviSurface *iviSurface) { m_iviSurfaces.append(iviSurface); } - void removeIviSurface(IviSurface *iviSurface) { m_iviSurfaces.removeOne(iviSurface); } - QVector<IviSurface *> m_iviSurfaces; - - friend class IviSurface; -}; - -} // namespace Impl - -#endif // MOCKIVIAPPLICATION_H diff --git a/tests/auto/client/shared/mockoutput.cpp b/tests/auto/client/shared/mockoutput.cpp deleted file mode 100644 index 13e0524ad..000000000 --- a/tests/auto/client/shared/mockoutput.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "mockoutput.h" - -#include <QDebug> - -namespace Impl { - -void Compositor::sendAddOutput(void *data, const QList<QVariant> ¶meters) { - Q_UNUSED(parameters); - Compositor *compositor = static_cast<Compositor *>(data); - auto output = new Output(compositor->m_display, QSize(1920, 1200), QPoint(0, 0)); - compositor->m_outputs.append(output); - - // Wait for the client to bind to the output - while (output->resourceMap().isEmpty()) - compositor->dispatchEvents(); -} - -void Compositor::sendRemoveOutput(void *data, const QList<QVariant> ¶meters) { - Compositor *compositor = static_cast<Compositor *>(data); - Q_ASSERT(compositor); - Output *output = resolveOutput(parameters.first()); - Q_ASSERT(output); - bool wasRemoved = compositor->m_outputs.removeOne(output); - Q_ASSERT(wasRemoved); - delete output; -} - -void Compositor::sendOutputGeometry(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Q_ASSERT(compositor); - Output *output = resolveOutput(parameters.first()); - Q_ASSERT(output); - QRect geometry = parameters.at(1).toRect(); - output->sendGeometryAndMode(geometry); -} - -void Compositor::setOutputMode(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - QSize size = parameters.first().toSize(); - Output *output = compositor->m_outputs.first(); - Q_ASSERT(output); - output->setCurrentMode(size); -} - -Output::Output(wl_display *display, const QSize &resolution, const QPoint &position) - : wl_output(display, 2) - , m_size(resolution) - , m_position(position) - , m_physicalSize(520, 320) - , m_mockOutput(new MockOutput(this)) -{ -} - -void Output::setCurrentMode(const QSize &size) -{ - m_size = size; - for (Resource *resource : resourceMap()) { - sendCurrentMode(resource); - send_done(resource->handle); - } -} - -void Output::sendGeometryAndMode(const QRect &geometry) -{ - m_size = geometry.size(); - m_position = geometry.topLeft(); - for (Resource *resource : resourceMap()) { - sendGeometry(resource); - sendCurrentMode(resource); - send_done(resource->handle); - } -} - -void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource) -{ - sendGeometry(resource); - sendCurrentMode(resource); - send_done(resource->handle); -} - -void Output::sendGeometry(Resource *resource) -{ - const int subPixel = 0; - const int transform = 0; - - send_geometry(resource->handle, - m_position.x(), m_position.y(), - m_physicalSize.width(), m_physicalSize.height(), - subPixel, "", "", transform ); -} - -void Output::sendCurrentMode(Resource *resource) -{ - send_mode(resource->handle, - WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED, - m_size.width(), m_size.height(), 60000); -} - -} // Impl - -MockOutput::MockOutput(Impl::Output *output) - : m_output(output) -{ -} diff --git a/tests/auto/client/shared/mockoutput.h b/tests/auto/client/shared/mockoutput.h deleted file mode 100644 index 9f261d5d7..000000000 --- a/tests/auto/client/shared/mockoutput.h +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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$ -** -****************************************************************************/ - -#ifndef MOCKOUTPUT_H -#define MOCKOUTPUT_H - -#include <qglobal.h> - -#include "qwayland-server-wayland.h" - -#include "mockcompositor.h" - -namespace Impl { - -class Output : public QtWaylandServer::wl_output -{ -public: - Output(::wl_display *display, const QSize &resolution, const QPoint &position); - - QSharedPointer<MockOutput> mockOutput() const { return m_mockOutput; } - void setCurrentMode(const QSize &size); - void sendGeometryAndMode(const QRect &geometry); - -protected: - void output_bind_resource(Resource *resource) override; - -private: - void sendGeometry(Resource *resource); - void sendCurrentMode(Resource *resource); - QSize m_size; - QPoint m_position; - const QSize m_physicalSize; - QSharedPointer<MockOutput> m_mockOutput; -}; - -} - -#endif // MOCKOUTPUT_H diff --git a/tests/auto/client/shared/mocksurface.cpp b/tests/auto/client/shared/mocksurface.cpp deleted file mode 100644 index 84dcda6b0..000000000 --- a/tests/auto/client/shared/mocksurface.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 "mocksurface.h" -#include "mockoutput.h" -#include "mockcompositor.h" -#include "mockwlshell.h" - -#include <QDebug> - -namespace Impl { - -void Compositor::sendSurfaceEnter(void *data, const QList<QVariant> ¶meters) -{ - Q_UNUSED(data); - Surface *surface = resolveSurface(parameters.at(0)); - Output *output = resolveOutput(parameters.at(1)); - Q_ASSERT(surface && surface->resource()); - Q_ASSERT(output); - auto outputResources = output->resourceMap().values(surface->resource()->client()); - Q_ASSERT(!outputResources.isEmpty()); - - for (auto outputResource : outputResources) - surface->send_enter(outputResource->handle); -} - -void Compositor::sendSurfaceLeave(void *data, const QList<QVariant> ¶meters) -{ - Q_UNUSED(data); - Surface *surface = resolveSurface(parameters.at(0)); - Output *output = resolveOutput(parameters.at(1)); - Q_ASSERT(surface && surface->resource()); - Q_ASSERT(output); - auto outputResources = output->resourceMap().values(surface->resource()->client()); - Q_ASSERT(!outputResources.isEmpty()); - - for (auto outputResource : outputResources) - surface->send_leave(outputResource->handle); -} - -void Compositor::sendShellSurfaceConfigure(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - Surface *surface = resolveSurface(parameters.at(0)); - QSize size = parameters.at(1).toSize(); - Q_ASSERT(size.isValid()); - if (auto toplevel = surface->xdgToplevelV6()) { - QVector<uint> states = { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED }; - auto statesBytes = QByteArray::fromRawData(reinterpret_cast<const char *>(states.data()), - states.size() * static_cast<int>(sizeof(uint))); - toplevel->send_configure(size.width(), size.height(), statesBytes); - toplevel->xdgSurface()->sendConfigure(compositor->nextSerial()); - } else if (auto wlShellSurface = surface->wlShellSurface()) { - const uint edges = 0; - wlShellSurface->send_configure(edges, size.width(), size.height()); - } else { - qWarning() << "The mocking framework doesn't know how to send a configure event for this surface"; - } -} - -Surface::Surface(wl_client *client, uint32_t id, int v, Compositor *compositor) - : QtWaylandServer::wl_surface(client, id, v) - , m_compositor(compositor) - , m_mockSurface(new MockSurface(this)) -{ -} - -Surface::~Surface() -{ - m_mockSurface->m_surface = 0; -} - -void Surface::map() -{ - m_mapped = true; -} - -bool Surface::isMapped() const -{ - return m_mapped; -} - -Surface *Surface::fromResource(struct ::wl_resource *resource) -{ - if (auto *r = Resource::fromResource(resource)) - return static_cast<Surface *>(r->surface_object); - return nullptr; -} - -void Surface::surface_destroy_resource(Resource *) -{ - compositor()->removeSurface(this); - delete this; -} - -void Surface::surface_destroy(Resource *resource) -{ - if (m_wlShellSurface) // on wl-shell the shell surface is automatically destroyed with the surface - wl_resource_destroy(m_wlShellSurface->resource()->handle); - Q_ASSERT(!m_wlShellSurface); - Q_ASSERT(!m_xdgSurfaceV6); - wl_resource_destroy(resource->handle); -} - -void Surface::surface_attach(Resource *resource, struct wl_resource *buffer, int x, int y) -{ - if (m_xdgSurfaceV6) { - // It's a protocol error to attach a buffer to an xdgSurface that's not configured - Q_ASSERT(xdgSurfaceV6()->configureSent()); - } - - Q_UNUSED(resource); - Q_UNUSED(x); - Q_UNUSED(y); - m_buffer = buffer; - - if (!buffer) - m_mockSurface->image = QImage(); -} - -void Surface::surface_damage(Resource *resource, - int32_t x, int32_t y, int32_t width, int32_t height) -{ - Q_UNUSED(resource); - Q_UNUSED(x); - Q_UNUSED(y); - Q_UNUSED(width); - Q_UNUSED(height); -} - -void Surface::surface_frame(Resource *resource, - uint32_t callback) -{ - wl_resource *frameCallback = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); - m_frameCallbackList << frameCallback; -} - -void Surface::surface_commit(Resource *resource) -{ - Q_UNUSED(resource); - - if (m_buffer) { - struct ::wl_shm_buffer *shm_buffer = wl_shm_buffer_get(m_buffer); - if (shm_buffer) { - int stride = wl_shm_buffer_get_stride(shm_buffer); - uint format = wl_shm_buffer_get_format(shm_buffer); - Q_UNUSED(format); - void *data = wl_shm_buffer_get_data(shm_buffer); - const uchar *char_data = static_cast<const uchar *>(data); - QImage img(char_data, wl_shm_buffer_get_width(shm_buffer), wl_shm_buffer_get_height(shm_buffer), stride, QImage::Format_ARGB32_Premultiplied); - m_mockSurface->image = img; - } - } - - foreach (wl_resource *frameCallback, m_frameCallbackList) { - wl_callback_send_done(frameCallback, m_compositor->time()); - wl_resource_destroy(frameCallback); - } - m_frameCallbackList.clear(); -} - -} -MockSurface::MockSurface(Impl::Surface *surface) - : m_surface(surface) -{ -} diff --git a/tests/auto/client/shared/mocksurface.h b/tests/auto/client/shared/mocksurface.h deleted file mode 100644 index 949dc23dd..000000000 --- a/tests/auto/client/shared/mocksurface.h +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ - -#ifndef MOCKSURFACE_H -#define MOCKSURFACE_H - -#include <qglobal.h> - -#include "qwayland-server-wayland.h" - -#include "mockcompositor.h" - -namespace Impl { - -class XdgToplevelV6; -class WlShellSurface; - -class Surface : public QtWaylandServer::wl_surface -{ -public: - Surface(wl_client *client, uint32_t id, int v, Compositor *compositor); - ~Surface(); - - Compositor *compositor() const { return m_compositor; } - static Surface *fromResource(struct ::wl_resource *resource); - void map(); - bool isMapped() const; - XdgSurfaceV6 *xdgSurfaceV6() const { return m_xdgSurfaceV6; } - XdgToplevelV6 *xdgToplevelV6() const { return m_xdgSurfaceV6 ? m_xdgSurfaceV6->toplevel() : nullptr; } - WlShellSurface *wlShellSurface() const { return m_wlShellSurface; } - - QSharedPointer<MockSurface> mockSurface() const { return m_mockSurface; } - -protected: - - void surface_destroy_resource(Resource *resource) override; - - void surface_destroy(Resource *resource) override; - void surface_attach(Resource *resource, - struct wl_resource *buffer, int x, int y) override; - void surface_damage(Resource *resource, - int32_t x, int32_t y, int32_t width, int32_t height) override; - void surface_frame(Resource *resource, - uint32_t callback) override; - void surface_commit(Resource *resource) override; -private: - wl_resource *m_buffer = nullptr; - XdgSurfaceV6 *m_xdgSurfaceV6 = nullptr; - WlShellSurface *m_wlShellSurface = nullptr; - - Compositor *m_compositor = nullptr; - QSharedPointer<MockSurface> m_mockSurface; - QList<wl_resource *> m_frameCallbackList; - bool m_mapped = false; - - friend class XdgSurfaceV6; - friend class WlShellSurface; -}; - -} - -#endif // MOCKSURFACE_H diff --git a/tests/auto/client/shared/mockwlshell.cpp b/tests/auto/client/shared/mockwlshell.cpp deleted file mode 100644 index 50e539932..000000000 --- a/tests/auto/client/shared/mockwlshell.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/**************************************************************************** -** -** 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 "mockwlshell.h" -#include "mocksurface.h" - -namespace Impl { - -WlShellSurface::WlShellSurface(wl_client *client, int id, Surface *surface) - : QtWaylandServer::wl_shell_surface(client, id, 1) - , m_surface(surface) -{ - surface->m_wlShellSurface = this; - surface->map(); -} - -WlShellSurface::~WlShellSurface() -{ - m_surface->m_wlShellSurface = nullptr; -} - -void WlShell::shell_get_shell_surface(QtWaylandServer::wl_shell::Resource *resource, uint32_t id, wl_resource *surface) -{ - new WlShellSurface(resource->client(), id, Surface::fromResource(surface)); -} - -} // namespace Impl diff --git a/tests/auto/client/shared/mockwlshell.h b/tests/auto/client/shared/mockwlshell.h deleted file mode 100644 index 3da586ca8..000000000 --- a/tests/auto/client/shared/mockwlshell.h +++ /dev/null @@ -1,58 +0,0 @@ -/**************************************************************************** -** -** 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 <qwayland-server-wayland.h> - -#ifndef MOCKWLSHELL_H -#define MOCKWLSHELL_H - -namespace Impl { - -class Surface; - -class WlShellSurface : public QtWaylandServer::wl_shell_surface -{ -public: - explicit WlShellSurface(::wl_client *client, int id, Surface *surface); - ~WlShellSurface() override; - void shell_surface_destroy_resource(Resource *) override { delete this; } - -private: - Surface *m_surface = nullptr; -}; - -class WlShell : public QtWaylandServer::wl_shell -{ -public: - explicit WlShell(::wl_display *display) : wl_shell(display, 1) {} - void shell_get_shell_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override; -}; - -} // namespace Impl - -#endif // MOCKWLSHELL_H diff --git a/tests/auto/client/shared/mockxdgshellv6.cpp b/tests/auto/client/shared/mockxdgshellv6.cpp deleted file mode 100644 index 05eff74ad..000000000 --- a/tests/auto/client/shared/mockxdgshellv6.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 "mockxdgshellv6.h" -#include "mocksurface.h" -#include "mockcompositor.h" - -namespace Impl { - -void Compositor::sendXdgToplevelV6Configure(void *data, const QList<QVariant> ¶meters) -{ - Compositor *compositor = static_cast<Compositor *>(data); - XdgToplevelV6 *toplevel = resolveToplevel(parameters.at(0)); - Q_ASSERT(toplevel && toplevel->resource()); - QSize size = parameters.at(1).toSize(); - Q_ASSERT(size.isValid()); - auto statesBytes = parameters.at(2).toByteArray(); - toplevel->send_configure(size.width(), size.height(), statesBytes); - toplevel->xdgSurface()->send_configure(compositor->nextSerial()); -} - -XdgSurfaceV6::XdgSurfaceV6(XdgShellV6 *shell, Surface *surface, wl_client *client, uint32_t id) - : QtWaylandServer::zxdg_surface_v6(client, id, 1) - , m_surface(surface) - , m_shell(shell) -{ - m_surface->m_xdgSurfaceV6 = this; -} - -XdgSurfaceV6::~XdgSurfaceV6() -{ - m_surface->m_xdgSurfaceV6 = nullptr; -} - -void XdgSurfaceV6::sendConfigure(uint32_t serial) -{ - send_configure(serial); - m_configureSent = true; -} - -void XdgSurfaceV6::zxdg_surface_v6_get_toplevel(QtWaylandServer::zxdg_surface_v6::Resource *resource, uint32_t id) -{ - int version = wl_resource_get_version(resource->handle); - m_toplevel = new XdgToplevelV6(this, resource->client(), id, version); -} - -void XdgSurfaceV6::zxdg_surface_v6_set_window_geometry(QtWaylandServer::zxdg_surface_v6::Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) -{ - Q_UNUSED(resource); - if (m_toplevel) { - QRect geometry(x, y, width, height); - emit m_toplevel->mockToplevel()->windowGeometryRequested(geometry); - } -} - -void XdgSurfaceV6::zxdg_surface_v6_destroy(QtWaylandServer::zxdg_surface_v6::Resource *resource) -{ - Q_ASSERT(!m_toplevel); - wl_resource_destroy(resource->handle); -} - -XdgToplevelV6::XdgToplevelV6(XdgSurfaceV6 *xdgSurface, wl_client *client, uint32_t id, int version) - : QtWaylandServer::zxdg_toplevel_v6(client, id, version) - , m_xdgSurface(xdgSurface) - , m_mockToplevel(new MockXdgToplevelV6(this)) -{ - auto *surface = m_xdgSurface->surface(); - m_xdgSurface->shell()->addToplevel(this); - surface->map(); -} - -XdgToplevelV6::~XdgToplevelV6() -{ - m_xdgSurface->shell()->removeToplevel(this); - m_mockToplevel->m_toplevel = nullptr; -} - -void XdgToplevelV6::zxdg_toplevel_v6_destroy(QtWaylandServer::zxdg_toplevel_v6::Resource *resource) -{ - m_xdgSurface->m_toplevel = nullptr; - wl_resource_destroy(resource->handle); -} - -void XdgToplevelV6::zxdg_toplevel_v6_set_minimized(QtWaylandServer::zxdg_toplevel_v6::Resource *resource) -{ - Q_UNUSED(resource); - emit m_mockToplevel->setMinimizedRequested(); -} - -void XdgToplevelV6::zxdg_toplevel_v6_set_maximized(QtWaylandServer::zxdg_toplevel_v6::Resource *resource) -{ - Q_UNUSED(resource); - emit m_mockToplevel->setMaximizedRequested(); -} - -void XdgToplevelV6::zxdg_toplevel_v6_unset_maximized(QtWaylandServer::zxdg_toplevel_v6::Resource *resource) -{ - Q_UNUSED(resource); - emit m_mockToplevel->unsetMaximizedRequested(); -} - -void XdgToplevelV6::zxdg_toplevel_v6_set_fullscreen(QtWaylandServer::zxdg_toplevel_v6::Resource *resource, wl_resource *output) -{ - Q_UNUSED(resource); - Q_UNUSED(output); - emit m_mockToplevel->setFullscreenRequested(); -} - -void XdgToplevelV6::zxdg_toplevel_v6_unset_fullscreen(QtWaylandServer::zxdg_toplevel_v6::Resource *resource) -{ - Q_UNUSED(resource); - emit m_mockToplevel->unsetFullscreenRequested(); -} - -void Impl::XdgShellV6::zxdg_shell_v6_get_xdg_surface(QtWaylandServer::zxdg_shell_v6::Resource *resource, uint32_t id, wl_resource *surface) -{ - new XdgSurfaceV6(this, Surface::fromResource(surface), resource->client(), id); -} - -} // namespace Impl diff --git a/tests/auto/client/shared/mockxdgshellv6.h b/tests/auto/client/shared/mockxdgshellv6.h deleted file mode 100644 index a238fa562..000000000 --- a/tests/auto/client/shared/mockxdgshellv6.h +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** 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 <qwayland-server-xdg-shell-unstable-v6.h> - -#include <QSharedPointer> -#include <QVector> - -#ifndef MOCKXDGSHELLV6_H -#define MOCKXDGSHELLV6_H - -class MockXdgToplevelV6; - -namespace Impl { - -class XdgToplevelV6; -class XdgShellV6; -class Surface; - -class XdgSurfaceV6 : public QtWaylandServer::zxdg_surface_v6 -{ -public: - XdgSurfaceV6(XdgShellV6 *shell, Surface *surface, wl_client *client, uint32_t id); - ~XdgSurfaceV6() override; - XdgShellV6 *shell() const { return m_shell; } - Surface *surface() const { return m_surface; } - XdgToplevelV6 *toplevel() const { return m_toplevel; } - - void sendConfigure(uint32_t serial); - bool configureSent() const { return m_configureSent; } - -protected: - void zxdg_surface_v6_destroy_resource(Resource *) override { delete this; } - void zxdg_surface_v6_get_toplevel(Resource *resource, uint32_t id) override; - void zxdg_surface_v6_set_window_geometry(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override; - void zxdg_surface_v6_destroy(Resource *resource) override; - -private: - Surface *m_surface = nullptr; - XdgToplevelV6 *m_toplevel = nullptr; - XdgShellV6 *m_shell = nullptr; - bool m_configureSent = false; - - friend class XdgToplevelV6; -}; - -class XdgToplevelV6 : public QtWaylandServer::zxdg_toplevel_v6 -{ -public: - XdgToplevelV6(XdgSurfaceV6 *xdgSurface, wl_client *client, uint32_t id, int version); - ~XdgToplevelV6() override; - XdgSurfaceV6 *xdgSurface() const { return m_xdgSurface; } - - QSharedPointer<MockXdgToplevelV6> mockToplevel() const { return m_mockToplevel; } - -protected: - void zxdg_toplevel_v6_destroy_resource(Resource *) override { delete this; } - void zxdg_toplevel_v6_destroy(Resource *resource) override; - void zxdg_toplevel_v6_set_minimized(Resource *resource) override; - void zxdg_toplevel_v6_set_maximized(Resource *resource) override; - void zxdg_toplevel_v6_unset_maximized(Resource *resource) override; - void zxdg_toplevel_v6_set_fullscreen(Resource *resource, struct ::wl_resource *output) override; - void zxdg_toplevel_v6_unset_fullscreen(Resource *resource) override; - -private: - XdgSurfaceV6 *m_xdgSurface = nullptr; - QSharedPointer<MockXdgToplevelV6> m_mockToplevel; -}; - -class XdgShellV6 : public QtWaylandServer::zxdg_shell_v6 -{ -public: - explicit XdgShellV6(::wl_display *display) : zxdg_shell_v6(display, 1) {} - QVector<XdgToplevelV6 *> toplevels() const { return m_toplevels; } - -protected: - void zxdg_shell_v6_get_xdg_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override; - -private: - void addToplevel(XdgToplevelV6 *toplevel) { m_toplevels.append(toplevel); } - void removeToplevel(XdgToplevelV6 *toplevel) { m_toplevels.removeOne(toplevel); } - QVector<XdgToplevelV6 *> m_toplevels; - - friend class XdgToplevelV6; -}; - -} // namespace Impl - -#endif // MOCKXDGSHELLV6_H diff --git a/tests/auto/client/shared/qttextinput.cpp b/tests/auto/client/shared/qttextinput.cpp new file mode 100644 index 000000000..1fb5ef1c4 --- /dev/null +++ b/tests/auto/client/shared/qttextinput.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qttextinput.h" + +namespace MockCompositor { + +QtTextInputManager::QtTextInputManager(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void QtTextInputManager::text_input_method_manager_v1_get_text_input_method(Resource *resource, uint32_t id, wl_resource *seatResource) +{ + Q_UNUSED(resource); + Q_UNUSED(id); + Q_UNUSED(seatResource); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/qttextinput.h b/tests/auto/client/shared/qttextinput.h new file mode 100644 index 000000000..047cec7d3 --- /dev/null +++ b/tests/auto/client/shared/qttextinput.h @@ -0,0 +1,26 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_QTTEXTINPUT_H +#define MOCKCOMPOSITOR_QTTEXTINPUT_H + +#include "coreprotocol.h" +#include <qwayland-server-qt-text-input-method-unstable-v1.h> + +#include <QtGui/qpa/qplatformnativeinterface.h> + +namespace MockCompositor { + +class QtTextInputManager : public Global, public QtWaylandServer::qt_text_input_method_manager_v1 +{ + Q_OBJECT +public: + QtTextInputManager(CoreCompositor *compositor); + +protected: + void text_input_method_manager_v1_get_text_input_method(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_QTTEXTINPUT_H diff --git a/tests/auto/client/shared/shared.pri b/tests/auto/client/shared/shared.pri index f3cb4d5a2..97202e787 100644 --- a/tests/auto/client/shared/shared.pri +++ b/tests/auto/client/shared/shared.pri @@ -1,31 +1,29 @@ -CONFIG += testcase link_pkgconfig -QT += testlib -QT += core-private gui-private waylandclient-private +QT += testlib waylandclient-private opengl +CONFIG += testcase wayland-scanner +QMAKE_USE += wayland-server -QMAKE_USE += wayland-client wayland-server - -CONFIG += wayland-scanner WAYLANDSERVERSOURCES += \ - ../../../../src/3rdparty/protocol/ivi-application.xml \ - ../../../../src/3rdparty/protocol/wayland.xml \ - ../../../../src/3rdparty/protocol/xdg-shell-unstable-v6.xml + $$PWD/../../../../src/3rdparty/protocol/wayland.xml \ + $$PWD/../../../../src/3rdparty/protocol/xdg-output-unstable-v1.xml \ + $$PWD/../../../../src/3rdparty/protocol/xdg-shell.xml \ + $$PWD/../../../../src/3rdparty/protocol/text-input-unstable-v2.xml INCLUDEPATH += ../shared -SOURCES += \ - ../shared/mockcompositor.cpp \ - ../shared/mockinput.cpp \ - ../shared/mockiviapplication.cpp \ - ../shared/mockwlshell.cpp \ - ../shared/mockxdgshellv6.cpp \ - ../shared/mocksurface.cpp \ - ../shared/mockoutput.cpp - HEADERS += \ - ../shared/mockcompositor.h \ - ../shared/mockinput.h \ - ../shared/mockiviapplication.h \ - ../shared/mockwlshell.h \ - ../shared/mockxdgshellv6.h \ - ../shared/mocksurface.h \ - ../shared/mockoutput.h + $$PWD/corecompositor.h \ + $$PWD/coreprotocol.h \ + $$PWD/datadevice.h \ + $$PWD/mockcompositor.h \ + $$PWD/xdgoutputv1.h \ + $$PWD/xdgshell.h \ + $$PWD/textinput.h + +SOURCES += \ + $$PWD/corecompositor.cpp \ + $$PWD/coreprotocol.cpp \ + $$PWD/datadevice.cpp \ + $$PWD/mockcompositor.cpp \ + $$PWD/xdgoutputv1.cpp \ + $$PWD/xdgshell.cpp \ + $$PWD/textinput.cpp diff --git a/tests/auto/client/shared/textinput.cpp b/tests/auto/client/shared/textinput.cpp new file mode 100644 index 000000000..fc4865d71 --- /dev/null +++ b/tests/auto/client/shared/textinput.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "textinput.h" + +namespace MockCompositor { + +TextInputManager::TextInputManager(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void TextInputManager::zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, wl_resource *seatResource) +{ + Q_UNUSED(seatResource); + add(resource->client(), id, resource->version()); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/textinput.h b/tests/auto/client/shared/textinput.h new file mode 100644 index 000000000..ca20ddbad --- /dev/null +++ b/tests/auto/client/shared/textinput.h @@ -0,0 +1,26 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_TEXTINPUT_H +#define MOCKCOMPOSITOR_TEXTINPUT_H + +#include "coreprotocol.h" +#include <qwayland-server-text-input-unstable-v2.h> + +#include <QtGui/qpa/qplatformnativeinterface.h> + +namespace MockCompositor { + +class TextInputManager : public Global, public QtWaylandServer::zwp_text_input_manager_v2 +{ + Q_OBJECT +public: + TextInputManager(CoreCompositor *compositor); + +protected: + void zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_TEXTINPUT_H diff --git a/tests/auto/client/shared/viewport.cpp b/tests/auto/client/shared/viewport.cpp new file mode 100644 index 000000000..df6bbb336 --- /dev/null +++ b/tests/auto/client/shared/viewport.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "viewport.h" + +namespace MockCompositor { + +Viewporter::Viewporter(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_viewporter(compositor->m_display, version) +{ +} + +void Viewporter::wp_viewporter_get_viewport(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource<Surface>(surface); + auto *viewport = new Viewport(s, resource->client(), id, resource->version()); + connect(viewport, &QObject::destroyed, this, [this, viewport]() { + m_viewports.removeOne(viewport); + }); + m_viewports << viewport; +} + +Viewport::Viewport(Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::wp_viewport(client, id, version) + , m_surface(surface) +{ +} + +void Viewport::wp_viewport_set_source(Resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) +{ + Q_UNUSED(resource) + m_source = QRectF(wl_fixed_to_double(x), + wl_fixed_to_double(y), + wl_fixed_to_double(width), + wl_fixed_to_double(height)); + Q_EMIT sourceChanged(); +} + +void Viewport::wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource) + + m_destination = QSize(width, height); + Q_EMIT destinationChanged(); +} + +void Viewport::wp_viewport_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void Viewport::wp_viewport_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} diff --git a/tests/auto/client/shared/viewport.h b/tests/auto/client/shared/viewport.h new file mode 100644 index 000000000..ddc4297db --- /dev/null +++ b/tests/auto/client/shared/viewport.h @@ -0,0 +1,50 @@ +// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_VIEWPORT_H +#define MOCKCOMPOSITOR_VIEWPORT_H + +#include "coreprotocol.h" +#include <qwayland-server-viewporter.h> + +namespace MockCompositor { + +class Viewport; + +class Viewporter : public Global, public QtWaylandServer::wp_viewporter +{ + Q_OBJECT +public: + explicit Viewporter(CoreCompositor *compositor, int version = 1); + QList<Viewport *> m_viewports; + +protected: + void wp_viewporter_get_viewport(Resource *resource, uint32_t id, wl_resource *surface) override; +}; + +class Viewport : public QObject, public QtWaylandServer::wp_viewport +{ + Q_OBJECT +public: + explicit Viewport(Surface *surface, wl_client *client, int id, int version); + + QRectF m_source; + QSize m_destination; + + Surface* m_surface; + +Q_SIGNALS: + void sourceChanged(); + void destinationChanged(); + +protected: + void wp_viewport_set_source(Resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) override; + void wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) override; + + void wp_viewport_destroy_resource(Resource *resource) override; + void wp_viewport_destroy(Resource *resource) override; +}; + +} + +#endif diff --git a/tests/auto/client/shared/xdgdialog.cpp b/tests/auto/client/shared/xdgdialog.cpp new file mode 100644 index 000000000..b973415bd --- /dev/null +++ b/tests/auto/client/shared/xdgdialog.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2024 David Redondo <kde@david-redondo.de> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "xdgdialog.h" + +#include "xdgshell.h" + +namespace MockCompositor { + +XdgDialog::XdgDialog(XdgWmDialog *wm, XdgToplevel *toplevel, wl_client *client, int id, int version) + : QtWaylandServer::xdg_dialog_v1(client, id, version), + toplevel(toplevel), + modal(false), + m_wm(wm) +{ +} + +void XdgDialog::xdg_dialog_v1_set_modal(Resource *resource) +{ + Q_UNUSED(resource) + modal = true; +} + +void XdgDialog::xdg_dialog_v1_unset_modal(Resource *resource) +{ + Q_UNUSED(resource) + modal = false; +} + +void XdgDialog::xdg_dialog_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void XdgDialog::xdg_dialog_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + m_wm->m_dialogs.removeOne(this); + delete this; +} + +XdgWmDialog::XdgWmDialog(CoreCompositor *compositor, int version) + : QtWaylandServer::xdg_wm_dialog_v1(compositor->m_display, version) +{ +} + +void XdgWmDialog::xdg_wm_dialog_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void XdgWmDialog::xdg_wm_dialog_v1_get_xdg_dialog(Resource *resource, uint32_t id, + struct ::wl_resource *toplevel) +{ + auto *t = fromResource<XdgToplevel>(toplevel); + auto *dialog = new XdgDialog(this, t, resource->client(), id, resource->version()); + m_dialogs.push_back(dialog); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/xdgdialog.h b/tests/auto/client/shared/xdgdialog.h new file mode 100644 index 000000000..d9e3de996 --- /dev/null +++ b/tests/auto/client/shared/xdgdialog.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 David Redondo <kde@david-redondo.de> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_XDG_DIALOG_H +#define MOCKCOMPOSITOR_XDG_DIALOG_H + +#include "corecompositor.h" +#include <qwayland-server-xdg-dialog-v1.h> + +namespace MockCompositor { + +class XdgToplevel; +class XdgWmDialog; + +class XdgDialog : public QtWaylandServer::xdg_dialog_v1 +{ +public: + explicit XdgDialog(XdgWmDialog *wm, XdgToplevel *toplevel, wl_client *client, int id, + int version); + XdgToplevel *toplevel; + bool modal; + +protected: + void xdg_dialog_v1_set_modal(Resource *resource) override; + void xdg_dialog_v1_unset_modal(Resource *resource) override; + void xdg_dialog_v1_destroy(Resource *resource) override; + void xdg_dialog_v1_destroy_resource(Resource *resource) override; + +private: + XdgWmDialog *m_wm; +}; + +class XdgWmDialog : public Global, public QtWaylandServer::xdg_wm_dialog_v1 +{ + Q_OBJECT +public: + explicit XdgWmDialog(CoreCompositor *compositor, int version = 1); + ~XdgWmDialog() = default; + QList<XdgDialog *> m_dialogs; + +protected: + void xdg_wm_dialog_v1_destroy(Resource *resource) override; + void xdg_wm_dialog_v1_get_xdg_dialog(Resource *resource, uint32_t id, + struct ::wl_resource *toplevel) override; +}; + +} // namespace MockCompositor + +#endif diff --git a/tests/auto/client/shared/xdgoutputv1.cpp b/tests/auto/client/shared/xdgoutputv1.cpp new file mode 100644 index 000000000..af72ae2eb --- /dev/null +++ b/tests/auto/client/shared/xdgoutputv1.cpp @@ -0,0 +1,34 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "xdgoutputv1.h" + +namespace MockCompositor { + +int XdgOutputV1::s_nextId = 1; + +void XdgOutputV1::sendLogicalSize(const QSize &size) +{ + m_logicalGeometry.setSize(size); + for (auto *resource : resourceMap()) + zxdg_output_v1::send_logical_size(resource->handle, size.width(), size.height()); +} + +void XdgOutputV1::addResource(wl_client *client, int id, int version) +{ + auto *resource = add(client, id, version)->handle; + zxdg_output_v1::send_logical_size(resource, m_logicalGeometry.width(), m_logicalGeometry.height()); + send_logical_position(resource, m_logicalGeometry.x(), m_logicalGeometry.y()); + if (version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) + send_name(resource, m_name); + if (version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION) + send_description(resource, m_description); + + if (version < 3) // zxdg_output_v1.done has been deprecated + zxdg_output_v1::send_done(resource); + else { + m_output->sendDone(client); + } +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/xdgoutputv1.h b/tests/auto/client/shared/xdgoutputv1.h new file mode 100644 index 000000000..8c6276741 --- /dev/null +++ b/tests/auto/client/shared/xdgoutputv1.h @@ -0,0 +1,63 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_XDGOUTPUTV1_H +#define MOCKCOMPOSITOR_XDGOUTPUTV1_H + +#include "coreprotocol.h" + +#include <qwayland-server-xdg-output-unstable-v1.h> + +namespace MockCompositor { + +class XdgOutputV1 : public QObject, public QtWaylandServer::zxdg_output_v1 +{ +public: + explicit XdgOutputV1(Output *output) + : m_output(output) + , m_logicalGeometry(m_output->m_data.position, QSize(m_output->m_data.mode.resolution / m_output->m_data.scale)) + , m_name(QString("WL-%1").arg(s_nextId++)) + {} + + void send_logical_size(int32_t width, int32_t height) = delete; + void sendLogicalSize(const QSize &size); + + void send_done() = delete; // zxdg_output_v1.done has been deprecated (in protocol version 3) + + void addResource(wl_client *client, int id, int version); + Output *m_output = nullptr; + QRect m_logicalGeometry; + QString m_name; + QString m_description = "This is an Xdg Output description"; + static int s_nextId; +}; + +class XdgOutputManagerV1 : public Global, public QtWaylandServer::zxdg_output_manager_v1 +{ + Q_OBJECT +public: + explicit XdgOutputManagerV1(CoreCompositor *compositor, int version = 3) + : QtWaylandServer::zxdg_output_manager_v1(compositor->m_display, version) + , m_version(version) + {} + int m_version = 1; // TODO: remove on libwayland upgrade + QMap<Output *, XdgOutputV1 *> m_xdgOutputs; + XdgOutputV1 *getXdgOutput(Output *output) + { + if (auto *xdgOutput = m_xdgOutputs.value(output)) + return xdgOutput; + return m_xdgOutputs[output] = new XdgOutputV1(output); // TODO: free memory + } + +protected: + void zxdg_output_manager_v1_get_xdg_output(Resource *resource, uint32_t id, wl_resource *outputResource) override + { + auto *output = fromResource<Output>(outputResource); + auto *xdgOutput = getXdgOutput(output); + xdgOutput->addResource(resource->client(), id, resource->version()); + } +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_XDGOUTPUTV1_H diff --git a/tests/auto/client/shared/xdgshell.cpp b/tests/auto/client/shared/xdgshell.cpp new file mode 100644 index 000000000..2c1639851 --- /dev/null +++ b/tests/auto/client/shared/xdgshell.cpp @@ -0,0 +1,222 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "xdgshell.h" + +namespace MockCompositor { + +XdgWmBase::XdgWmBase(CoreCompositor *compositor, int version) + : QtWaylandServer::xdg_wm_base(compositor->m_display, version) + , m_compositor(compositor) +{ +} + +XdgToplevel *XdgWmBase::toplevel(int i) +{ + int j = 0; + for (auto *xdgSurface : std::as_const(m_xdgSurfaces)) { + if (auto *toplevel = xdgSurface->m_toplevel) { + if (j == i) + return toplevel; + ++j; + } + } + return nullptr; +} + +XdgPopup *XdgWmBase::popup(int i) +{ + int j = 0; + for (auto *xdgSurface : std::as_const(m_xdgSurfaces)) { + if (auto *popup = xdgSurface->m_popup) { + if (j == i) + return popup; + ++j; + } + } + return nullptr; +} + +void XdgWmBase::xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource<Surface>(surface); + auto *xdgSurface = new XdgSurface(this, s, resource->client(), id, resource->version()); + m_xdgSurfaces << xdgSurface; + emit xdgSurfaceCreated(xdgSurface); +} + +void XdgWmBase::xdg_wm_base_pong(Resource *resource, uint32_t serial) +{ + Q_UNUSED(resource); + emit pong(serial); +} + +XdgSurface::XdgSurface(XdgWmBase *xdgWmBase, Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::xdg_surface(client, id, version) + , m_xdgWmBase(xdgWmBase) + , m_surface(surface) +{ + QVERIFY(!surface->m_pending.buffer); + QVERIFY(!surface->m_committed.buffer); + connect(this, &XdgSurface::toplevelCreated, xdgWmBase, &XdgWmBase::toplevelCreated, Qt::DirectConnection); + connect(surface, &Surface::attach, this, &XdgSurface::verifyConfigured); + connect(surface, &Surface::commit, this, [this] { + m_committed = m_pending; + + if (m_ackedConfigureSerial != m_committedConfigureSerial) { + m_committedConfigureSerial = m_ackedConfigureSerial; + emit configureCommitted(m_committedConfigureSerial); + } + }); +} + +void XdgSurface::sendConfigure(uint serial) +{ + Q_ASSERT(serial); + m_pendingConfigureSerials.append(serial); + m_configureSent = true; + xdg_surface::send_configure(serial); +} + +uint XdgSurface::sendConfigure() +{ + const uint serial = m_xdgWmBase->m_compositor->nextSerial(); + sendConfigure(serial); + return serial; +} + +void XdgSurface::xdg_surface_get_toplevel(Resource *resource, uint32_t id) +{ + QVERIFY(!m_toplevel); + QVERIFY(!m_popup); + m_toplevel = new XdgToplevel(this, id, resource->version()); + emit toplevelCreated(m_toplevel); +} + +void XdgSurface::xdg_surface_get_popup(Resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner) +{ + Q_UNUSED(positioner); + QVERIFY(!m_toplevel); + QVERIFY(!m_popup); + auto *p = fromResource<XdgSurface>(parent); + m_popup = new XdgPopup(this, p, id, resource->version()); +} + +void XdgSurface::xdg_surface_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + bool removed = m_xdgWmBase->m_xdgSurfaces.removeOne(this); + Q_ASSERT(removed); + delete this; +} + +void XdgSurface::xdg_surface_destroy(QtWaylandServer::xdg_surface::Resource *resource) +{ + QVERIFY(m_popups.empty()); + wl_resource_destroy(resource->handle); +} + +void XdgSurface::xdg_surface_set_window_geometry(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + QRect rect(x, y, width, height); + QVERIFY(rect.isValid()); + m_pending.windowGeometry = rect; +} + +void XdgSurface::xdg_surface_ack_configure(Resource *resource, uint32_t serial) +{ + Q_UNUSED(resource); + QVERIFY2(m_pendingConfigureSerials.contains(serial), qPrintable(QString::number(serial))); + m_ackedConfigureSerial = serial; + while (!m_pendingConfigureSerials.empty()) { + uint s = m_pendingConfigureSerials.takeFirst(); + if (s == serial) + return; + } +} + +XdgToplevel::XdgToplevel(XdgSurface *xdgSurface, int id, int version) + : QtWaylandServer::xdg_toplevel(xdgSurface->resource()->client(), id, version) + , m_xdgSurface(xdgSurface) +{ + connect(surface(), &Surface::commit, this, [this] { m_committed = m_pending; }); +} + +void XdgToplevel::sendConfigureBounds(const QSize &size) +{ + send_configure_bounds(size.width(), size.height()); +} + +void XdgToplevel::sendConfigure(const QSize &size, const QList<uint> &states) +{ + send_configure(size.width(), size.height(), toByteArray(states)); +} + +uint XdgToplevel::sendCompleteConfigure(const QSize &size, const QList<uint> &states) +{ + sendConfigure(size, states); + return m_xdgSurface->sendConfigure(); +} + +void XdgToplevel::xdg_toplevel_set_max_size(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + QSize size(width, height); + QVERIFY(size.isValid()); + m_pending.maxSize = size; +} + +void XdgToplevel::xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + QSize size(width, height); + QVERIFY(size.isValid()); + m_pending.minSize = size; +} + +XdgPopup::XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version) + : QtWaylandServer::xdg_popup(xdgSurface->resource()->client(), id, version) + , m_xdgSurface(xdgSurface) + , m_parentXdgSurface(parent) +{ + Q_ASSERT(m_xdgSurface); + Q_ASSERT(m_parentXdgSurface); + m_parentXdgSurface->m_popups << this; +} + +void XdgPopup::sendConfigure(const QRect &geometry) +{ + send_configure(geometry.x(), geometry.y(), geometry.width(), geometry.height()); +} + +uint XdgPopup::sendCompleteConfigure(const QRect &geometry) +{ + sendConfigure(geometry); + return m_xdgSurface->sendConfigure(); +} + +void XdgPopup::xdg_popup_grab(QtWaylandServer::xdg_popup::Resource *resource, wl_resource *seat, uint32_t serial) +{ + Q_UNUSED(resource); + Q_UNUSED(seat); // TODO: verify correct seat as well + QVERIFY(!m_grabbed); + QVERIFY(m_parentXdgSurface->isValidPopupGrabParent()); + m_xdgSurface->m_xdgWmBase->m_topmostGrabbingPopup = this; + m_grabbed = true; + m_grabSerial = serial; +} + +void XdgPopup::xdg_popup_destroy(Resource *resource) { + Q_UNUSED(resource); + if (m_grabbed) { + auto *base = m_xdgSurface->m_xdgWmBase; + QCOMPARE(base->m_topmostGrabbingPopup, this); + base->m_topmostGrabbingPopup = this->m_parentXdgSurface->m_popup; + } + m_xdgSurface->m_popup = nullptr; + m_parentXdgSurface->m_popups.removeAll(this); + emit destroyRequested(); +} + +} // namespace MockCompositor diff --git a/tests/auto/client/shared/xdgshell.h b/tests/auto/client/shared/xdgshell.h new file mode 100644 index 000000000..3959e0668 --- /dev/null +++ b/tests/auto/client/shared/xdgshell.h @@ -0,0 +1,130 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_XDGSHELL_H +#define MOCKCOMPOSITOR_XDGSHELL_H + +#include "coreprotocol.h" +#include <qwayland-server-xdg-shell.h> + +namespace MockCompositor { + +class XdgSurface; +class XdgToplevel; +class XdgPopup; +using XdgPositioner = QtWaylandServer::xdg_positioner; + +class XdgWmBase : public Global, public QtWaylandServer::xdg_wm_base +{ + Q_OBJECT +public: + explicit XdgWmBase(CoreCompositor *compositor, int version = 4); + using QtWaylandServer::xdg_wm_base::send_ping; + void send_ping(uint32_t) = delete; // It's a global, use resource specific instead + bool isClean() override { return m_xdgSurfaces.empty(); } + QString dirtyMessage() override { return m_xdgSurfaces.empty() ? "clean" : "remaining xdg surfaces"; } + QList<XdgSurface *> m_xdgSurfaces; + XdgToplevel *toplevel(int i = 0); + XdgPopup *popup(int i = 0); + XdgPopup *m_topmostGrabbingPopup = nullptr; + CoreCompositor *m_compositor = nullptr; + +signals: + void pong(uint serial); + void xdgSurfaceCreated(XdgSurface *xdgSurface); + void toplevelCreated(XdgToplevel *toplevel); + +protected: + void xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override; + void xdg_wm_base_pong(Resource *resource, uint32_t serial) override; + void xdg_wm_base_create_positioner(Resource *resource, uint32_t id) override + { + new XdgPositioner(resource->client(), id, resource->version()); + } +}; + +class XdgSurface : public QObject, public QtWaylandServer::xdg_surface +{ + Q_OBJECT +public: + explicit XdgSurface(XdgWmBase *xdgWmBase, Surface *surface, wl_client *client, int id, int version); + void send_configure(uint serial) = delete; // Use the one below instead, as it tracks state + void sendConfigure(uint serial); + uint sendConfigure(); + bool isTopmostGrabbingPopup() const { return m_popup && m_xdgWmBase->m_topmostGrabbingPopup == m_popup; } + bool isValidPopupGrabParent() const { return isTopmostGrabbingPopup() || (m_toplevel && !m_xdgWmBase->m_topmostGrabbingPopup); } + + // Role objects + XdgToplevel *m_toplevel = nullptr; + XdgPopup *m_popup = nullptr; + + XdgWmBase *m_xdgWmBase = nullptr; + Surface *m_surface = nullptr; + bool m_configureSent = false; + QList<uint> m_pendingConfigureSerials; + uint m_ackedConfigureSerial = 0; + uint m_committedConfigureSerial = 0; + struct DoubleBufferedState { + QRect windowGeometry = {0, 0, 0, 0}; + } m_pending, m_committed; + QList<XdgPopup *> m_popups; + +public slots: + void verifyConfigured() { QVERIFY(m_configureSent); } + +signals: + void configureCommitted(uint); + void toplevelCreated(XdgToplevel *toplevel); + +protected: + void xdg_surface_get_toplevel(Resource *resource, uint32_t id) override; + void xdg_surface_get_popup(Resource *resource, uint32_t id, ::wl_resource *parent, ::wl_resource *positioner) override; + void xdg_surface_destroy_resource(Resource *resource) override; + void xdg_surface_destroy(Resource *resource) override; + void xdg_surface_set_window_geometry(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override; + void xdg_surface_ack_configure(Resource *resource, uint32_t serial) override; +}; + +class XdgToplevel : public QObject, public QtWaylandServer::xdg_toplevel +{ + Q_OBJECT +public: + explicit XdgToplevel(XdgSurface *xdgSurface, int id, int version = 1); + void sendConfigureBounds(const QSize &size); + void sendConfigure(const QSize &size = {0, 0}, const QList<uint> &states = {}); + uint sendCompleteConfigure(const QSize &size = {0, 0}, const QList<uint> &states = {}); + Surface *surface() { return m_xdgSurface->m_surface; } + + XdgSurface *m_xdgSurface = nullptr; + struct DoubleBufferedState { + QSize minSize = {0, 0}; + QSize maxSize = {0, 0}; + } m_pending, m_committed; + +protected: + void xdg_toplevel_set_max_size(Resource *resource, int32_t width, int32_t height) override; + void xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) override; +}; + +class XdgPopup : public QObject, public QtWaylandServer::xdg_popup +{ + Q_OBJECT +public: + explicit XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version = 1); + void sendConfigure(const QRect &geometry); + uint sendCompleteConfigure(const QRect &geometry); + Surface *surface() { return m_xdgSurface->m_surface; } + XdgSurface *m_xdgSurface = nullptr; + XdgSurface *m_parentXdgSurface = nullptr; + bool m_grabbed = false; + uint m_grabSerial = 0; +signals: + void destroyRequested(); +protected: + void xdg_popup_grab(Resource *resource, ::wl_resource *seat, uint32_t serial) override; + void xdg_popup_destroy(Resource *resource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_XDGSHELL_H diff --git a/tests/auto/client/surface/CMakeLists.txt b/tests/auto/client/surface/CMakeLists.txt new file mode 100644 index 000000000..b175a5331 --- /dev/null +++ b/tests/auto/client/surface/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from surface.pro. + +##################################################################### +## tst_surface Test: +##################################################################### + +qt_internal_add_test(tst_surface + SOURCES + tst_surface.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/surface/tst_surface.cpp b/tests/auto/client/surface/tst_surface.cpp new file mode 100644 index 000000000..06e625155 --- /dev/null +++ b/tests/auto/client/surface/tst_surface.cpp @@ -0,0 +1,216 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#if QT_CONFIG(opengl) +#include <QtOpenGL/QOpenGLWindow> +#endif + +using namespace MockCompositor; + +class tst_surface : public QObject, private DefaultCompositor +{ + Q_OBJECT +public: + explicit tst_surface(); +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void createDestroySurface(); + void waitForFrameCallbackRaster(); +#if QT_CONFIG(opengl) + void waitForFrameCallbackGl(); +#endif + void negotiateShmFormat(); + + // Subsurfaces + void createSubsurface(); + void createSubsurfaceForHiddenParent(); +}; + +tst_surface::tst_surface() +{ + m_config.autoFrameCallback = false; +} + +void tst_surface::createDestroySurface() +{ + QWindow window; + window.show(); + + QCOMPOSITOR_TRY_VERIFY(surface()); + + window.destroy(); + QCOMPOSITOR_TRY_VERIFY(!surface()); +} + +void tst_surface::waitForFrameCallbackRaster() +{ + class TestWindow : public QRasterWindow { + public: + explicit TestWindow() { resize(40, 40); } + void paintEvent(QPaintEvent *event) override + { + Q_UNUSED(event); + update(); + } + }; + TestWindow window; + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy bufferSpy(exec([&] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + // We should get the first buffer without waiting for a frame callback + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + + // Make sure we follow frame callbacks for some frames + for (int i = 0; i < 5; ++i) { + xdgPingAndWaitForPong(); // Make sure things have happened on the client + exec([&] { + QVERIFY(bufferSpy.empty()); // Make sure no extra buffers have arrived + QVERIFY(!xdgToplevel()->surface()->m_waitingFrameCallbacks.empty()); + xdgToplevel()->surface()->sendFrameCallbacks(); + }); + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + } +} + +#if QT_CONFIG(opengl) +void tst_surface::waitForFrameCallbackGl() +{ + class TestWindow : public QOpenGLWindow { + public: + explicit TestWindow() + { + resize(40, 40); + connect(this, &QOpenGLWindow::frameSwapped, + this, QOverload<>::of(&QPaintDeviceWindow::update)); + update(); + } + void paintGL() override + { + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + }; + TestWindow window; + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy bufferSpy(exec([&] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + // We should get the first buffer without waiting for a frame callback + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + + // Make sure we follow frame callbacks for some frames + for (int i = 0; i < 5; ++i) { + xdgPingAndWaitForPong(); // Make sure things have happened on the client + if (!qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_WINDOWDECORATION") && i == 0) { + QCOMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + } + exec([&] { + QVERIFY(bufferSpy.empty()); // Make sure no extra buffers have arrived + QVERIFY(!xdgToplevel()->surface()->m_waitingFrameCallbacks.empty()); + xdgToplevel()->surface()->sendFrameCallbacks(); + }); + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + } +} +#endif // QT_CONFIG(opengl) + +void tst_surface::negotiateShmFormat() +{ + QSKIP("TODO: I'm not sure why we're choosing xrgb over argb in this case..."); + QRasterWindow window; + window.setFlag(Qt::FramelessWindowHint); // decorations force alpha + QSurfaceFormat format; + format.setAlphaBufferSize(0); + window.setFormat(format); + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy bufferSpy(exec([&] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted); + const uint serial = exec([&] { return xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial); + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + auto *shmBuffer = ShmBuffer::fromBuffer(buffer); + QVERIFY(shmBuffer); + qDebug() << "shmBuffer->m_format" << shmBuffer->m_format; + QCOMPARE(shmBuffer->m_format, Shm::format_xrgb8888); + }); +} + +void tst_surface::createSubsurface() +{ + QRasterWindow window; + window.setObjectName("main"); + window.resize(200, 200); + + QRasterWindow subWindow; + subWindow.setObjectName("subwindow"); + subWindow.setParent(&window); + subWindow.resize(64, 64); + + window.show(); + subWindow.show(); + + QCOMPOSITOR_TRY_VERIFY(subSurface()); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + + const Surface *mainSurface = exec([&] {return surface(0);}); + const Surface *childSurface = exec([&] {return surface(1);}); + QSignalSpy mainSurfaceCommitSpy(mainSurface, &Surface::commit); + QSignalSpy childSurfaceCommitSpy(childSurface, &Surface::commit); + + // Move subsurface. The parent should redraw and commit + subWindow.setGeometry(100, 100, 64, 64); + // the toplevel should commit to indicate the subsurface moved + QCOMPOSITOR_TRY_COMPARE(mainSurfaceCommitSpy.size(), 1); + mainSurfaceCommitSpy.clear(); + childSurfaceCommitSpy.clear(); + + // Move and resize the subSurface. The parent should redraw and commit + // The child should also redraw + subWindow.setGeometry(50, 50, 80, 80); + QCOMPOSITOR_TRY_COMPARE(mainSurfaceCommitSpy.size(), 1); + QCOMPOSITOR_TRY_COMPARE(childSurfaceCommitSpy.size(), 1); + +} + +// Used to cause a crash in libwayland (QTBUG-79674) +void tst_surface::createSubsurfaceForHiddenParent() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + + window.hide(); + + QRasterWindow subWindow; + subWindow.setParent(&window); + subWindow.resize(64, 64); + subWindow.show(); + + // Make sure the client doesn't quit before it has a chance to crash + xdgPingAndWaitForPong(); + + // Make sure the subsurface was actually created + const Subsurface *subsurface = exec([&] {return subSurface(0);}); + QVERIFY(subsurface); +} + +QCOMPOSITOR_TEST_MAIN(tst_surface) +#include "tst_surface.moc" diff --git a/tests/auto/client/tabletv2/CMakeLists.txt b/tests/auto/client/tabletv2/CMakeLists.txt new file mode 100644 index 000000000..1400a511a --- /dev/null +++ b/tests/auto/client/tabletv2/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from tabletv2.pro. + +##################################################################### +## tst_tabletv2 Test: +##################################################################### + +qt_internal_add_test(tst_tabletv2 + SOURCES + tst_tabletv2.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/tabletv2/tst_tabletv2.cpp b/tests/auto/client/tabletv2/tst_tabletv2.cpp new file mode 100644 index 000000000..85df099f9 --- /dev/null +++ b/tests/auto/client/tabletv2/tst_tabletv2.cpp @@ -0,0 +1,893 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <qwayland-server-tablet-unstable-v2.h> + +#include <QtGui/QRasterWindow> + +using namespace MockCompositor; + +constexpr int tabletVersion = 1; // protocol VERSION, not the name suffix (_v2) + +class TabletManagerV2; +class TabletSeatV2; + +class TabletV2 : public QObject, public QtWaylandServer::zwp_tablet_v2 +{ + Q_OBJECT +public: + explicit TabletV2(TabletSeatV2 *tabletSeat) + : m_tabletSeat(tabletSeat) + { + } + + void send_removed() = delete; + void send_removed(struct ::wl_resource *resource) = delete; + void sendRemoved(); + + QPointer<TabletSeatV2> m_tabletSeat; // destroy order is not guaranteed +protected: + void zwp_tablet_v2_destroy(Resource *resource) override; +}; + +class TabletToolV2 : public QObject, public QtWaylandServer::zwp_tablet_tool_v2 +{ + Q_OBJECT +public: + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + explicit TabletToolV2(TabletSeatV2 *tabletSeat, ToolType toolType, quint64 hardwareSerial) + : m_tabletSeat(tabletSeat) + , m_toolType(toolType) + , m_hardwareSerial(hardwareSerial) + { + } + + wl_resource *toolResource() // for convenience + { + Q_ASSERT(resourceMap().size() == 1); + // Strictly speaking, there may be more than one resource for the tool, for intsance if + // if there are multiple clients, or a client has called get_tablet_seat multiple times. + // For now we'll pretend there can only be one resource. + return resourceMap().first()->handle; + } + + void send_removed() = delete; + void send_removed(struct ::wl_resource *resource) = delete; + void sendRemoved(); + + uint sendProximityIn(TabletV2 *tablet, Surface *surface); + void sendProximityOut(); + void sendMotion(QPointF position) + { + Q_ASSERT(m_proximitySurface); + for (auto *resource : resourceMap()) + send_motion(resource->handle, wl_fixed_from_double(position.x()), wl_fixed_from_double(position.y())); + } + uint sendDown(); + void sendUp() { send_up(toolResource()); } + void sendPressure(uint pressure); + void sendTilt(qreal tiltX, qreal tiltY) { send_tilt(toolResource(), wl_fixed_from_double(tiltX), wl_fixed_from_double(tiltY)); } + void sendRotation(qreal rotation) { send_rotation(toolResource(), wl_fixed_from_double(rotation)); } + uint sendButton(uint button, bool pressed); + uint sendFrame(); + + QPointer<TabletSeatV2> m_tabletSeat; // destruction order is not guaranteed + ToolType m_toolType = ToolType::type_pen; + quint64 m_hardwareSerial = 0; + QPointer<Surface> m_proximitySurface; +protected: + void zwp_tablet_tool_v2_destroy(Resource *resource) override; +}; + +class TabletPadV2 : public QObject, public QtWaylandServer::zwp_tablet_pad_v2 +{ + Q_OBJECT +public: + explicit TabletPadV2(TabletSeatV2 *tabletSeat) + : m_tabletSeat(tabletSeat) + { + } + + void send_removed() = delete; + void send_removed(struct ::wl_resource *resource) = delete; + void sendRemoved(); + + QPointer<TabletSeatV2> m_tabletSeat; // destroy order is not guaranteed +protected: + void zwp_tablet_pad_v2_destroy(Resource *resource) override; +}; + +class TabletSeatV2 : public QObject, public QtWaylandServer::zwp_tablet_seat_v2 +{ + Q_OBJECT +public: + explicit TabletSeatV2(TabletManagerV2 *manager, Seat *seat) + : m_manager(manager) + , m_seat(seat) + {} + TabletV2 *addTablet() + { + auto *tablet = new TabletV2(this); + m_tablets.append(tablet); + for (auto *resource : resourceMap()) + sendTabletAdded(resource, tablet); + return tablet; + } + + void sendTabletAdded(Resource *resource, TabletV2 *tablet) + { + // Although, not necessarily correct, assuming just one tablet_seat per client + auto *tabletResource = tablet->add(resource->client(), resource->version()); + zwp_tablet_seat_v2::send_tablet_added(resource->handle, tabletResource->handle); + // TODO: send extra stuff before done? + tablet->send_done(tabletResource->handle); + } + + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + TabletToolV2 *addTool(ToolType toolType = ToolType::type_pen, quint64 hardwareSerial = 0) + { + auto *tool = new TabletToolV2(this, toolType, hardwareSerial); + m_tools.append(tool); + for (auto *resource : resourceMap()) + sendToolAdded(resource, tool); + return tool; + } + + void sendToolAdded(Resource *resource, TabletToolV2 *tool) + { + // Although, not necessarily correct, assuming just one tablet_seat per client + auto *toolResource = tool->add(resource->client(), resource->version())->handle; + zwp_tablet_seat_v2::send_tool_added(resource->handle, toolResource); + tool->send_type(toolResource, tool->m_toolType); + if (tool->m_hardwareSerial) { + const uint hi = tool->m_hardwareSerial >> 32; + const uint lo = tool->m_hardwareSerial & 0xffffffff; + tool->send_hardware_serial(toolResource, hi, lo); + } + tool->send_done(toolResource); + } + + TabletPadV2 *addPad() + { + auto *pad = new TabletPadV2(this); + m_pads.append(pad); + for (auto *resource : resourceMap()) + sendPadAdded(resource, pad); + return pad; + } + + void sendPadAdded(Resource *resource, TabletPadV2 *pad) + { + // Although, not necessarily correct, assuming just one tablet_seat per client + auto *padResource = pad->add(resource->client(), resource->version())->handle; + zwp_tablet_seat_v2::send_pad_added(resource->handle, padResource); + pad->send_done(padResource); + } + + void removeAll() + { + const auto tools = m_tools; + for (auto *tool : tools) + tool->sendRemoved(); + + const auto tablets = m_tablets; + for (auto *tablet : tablets) + tablet->sendRemoved(); + + const auto pads = m_pads; + for (auto *pad : pads) + pad->sendRemoved(); + } + + TabletManagerV2 *m_manager = nullptr; + Seat *m_seat = nullptr; + QList<TabletV2 *> m_tablets; + QList<TabletV2 *> m_tabletsWaitingForDestroy; + QList<TabletToolV2 *> m_tools; + QList<TabletToolV2 *> m_toolsWaitingForDestroy; + QList<TabletPadV2 *> m_pads; + QList<TabletPadV2 *> m_padsWaitingForDestroy; + +protected: + void zwp_tablet_seat_v2_bind_resource(Resource *resource) override + { + for (auto *tablet : m_tablets) + sendTabletAdded(resource, tablet); + for (auto *tool : m_tools) + sendToolAdded(resource, tool); + for (auto *pad : m_pads) + sendPadAdded(resource, pad); + } +}; + +class TabletManagerV2 : public Global, public QtWaylandServer::zwp_tablet_manager_v2 +{ + Q_OBJECT +public: + explicit TabletManagerV2(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::zwp_tablet_manager_v2(compositor->m_display, version) + , m_version(version) + {} + bool isClean() override + { + for (auto *seat : m_tabletSeats) { + if (!seat->m_tabletsWaitingForDestroy.empty()) + return false; + if (!seat->m_toolsWaitingForDestroy.empty()) + return false; + if (!seat->m_padsWaitingForDestroy.empty()) + return false; + } + return true; + } + + TabletSeatV2 *tabletSeatFor(Seat *seat) + { + Q_ASSERT(seat); + if (auto *tabletSeat = m_tabletSeats.value(seat, nullptr)) + return tabletSeat; + + auto *tabletSeat = new TabletSeatV2(this, seat); + m_tabletSeats[seat] = tabletSeat; + return tabletSeat; + } + + int m_version = 1; // TODO: Remove on libwayland upgrade + QMap<Seat *, TabletSeatV2 *> m_tabletSeats; + +protected: + void zwp_tablet_manager_v2_destroy(Resource *resource) override + { + // tablet_seats created from this object are unaffected and should be destroyed separately. + wl_resource_destroy(resource->handle); + } + + void zwp_tablet_manager_v2_get_tablet_seat(Resource *resource, uint32_t id, ::wl_resource *seatResource) override + { + auto *seat = fromResource<Seat>(seatResource); + QVERIFY(seat); + auto *tabletSeat = tabletSeatFor(seat); + tabletSeat->add(resource->client(), id, resource->version()); + } +}; + +void TabletV2::sendRemoved() +{ + for (auto *resource : resourceMap()) + zwp_tablet_v2_send_removed(resource->handle); + bool removed = m_tabletSeat->m_tablets.removeOne(this); + QVERIFY(removed); + m_tabletSeat->m_tabletsWaitingForDestroy.append(this); +} + +void TabletV2::zwp_tablet_v2_destroy(QtWaylandServer::zwp_tablet_v2::Resource *resource) +{ + Q_UNUSED(resource); + if (m_tabletSeat) { + bool removed = m_tabletSeat->m_tabletsWaitingForDestroy.removeOne(this); + QVERIFY(removed); + } + wl_resource_destroy(resource->handle); +} + +void TabletToolV2::sendRemoved() +{ + for (auto *resource : resourceMap()) + zwp_tablet_tool_v2_send_removed(resource->handle); + bool removed = m_tabletSeat->m_tools.removeOne(this); + QVERIFY(removed); + m_tabletSeat->m_toolsWaitingForDestroy.append(this); +} + +uint TabletToolV2::sendProximityIn(TabletV2 *tablet, Surface *surface) +{ + Q_ASSERT(!m_proximitySurface); + m_proximitySurface = surface; + uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial(); + auto *client = surface->resource()->client(); + auto tabletResource = tablet->resourceMap().value(client)->handle; + send_proximity_in(toolResource(), serial, tabletResource, surface->resource()->handle); + return serial; +} + +void TabletToolV2::sendProximityOut() +{ + Q_ASSERT(m_proximitySurface); + send_proximity_out(toolResource()); + m_proximitySurface = nullptr; +} + +uint TabletToolV2::sendDown() +{ + uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial(); + send_down(toolResource(), serial); + return serial; +} + +void TabletToolV2::sendPressure(uint pressure) +{ + Q_ASSERT(m_proximitySurface); + auto *client = m_proximitySurface->resource()->client(); + auto toolResource = resourceMap().value(client)->handle; + send_pressure(toolResource, pressure); +} + +uint TabletToolV2::sendButton(uint button, bool pressed) +{ + button_state state = pressed ? button_state_pressed : button_state_released; + uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial(); + send_button(toolResource(), serial, button, state); + return serial; +} + +uint TabletToolV2::sendFrame() +{ + uint time = m_tabletSeat->m_seat->m_compositor->currentTimeMilliseconds(); + for (auto *resource : resourceMap()) + send_frame(resource->handle, time); + return time; +} + +void TabletToolV2::zwp_tablet_tool_v2_destroy(QtWaylandServer::zwp_tablet_tool_v2::Resource *resource) +{ + if (m_tabletSeat) { + bool removed = m_tabletSeat->m_toolsWaitingForDestroy.removeOne(this); + QVERIFY(removed); + } + wl_resource_destroy(resource->handle); +} + +void TabletPadV2::sendRemoved() +{ + for (auto *resource : resourceMap()) + zwp_tablet_pad_v2_send_removed(resource->handle); + bool removed = m_tabletSeat->m_pads.removeOne(this); + QVERIFY(removed); + m_tabletSeat->m_padsWaitingForDestroy.append(this); +} + +void TabletPadV2::zwp_tablet_pad_v2_destroy(QtWaylandServer::zwp_tablet_pad_v2::Resource *resource) +{ + if (m_tabletSeat) { + bool removed = m_tabletSeat->m_padsWaitingForDestroy.removeOne(this); + QVERIFY(removed); + } + wl_resource_destroy(resource->handle); +} + +class TabletCompositor : public DefaultCompositor { +public: + explicit TabletCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add<TabletManagerV2>(tabletVersion); + }); + } + TabletSeatV2 *tabletSeat(int i = 0) + { + return get<TabletManagerV2>()->tabletSeatFor(get<Seat>(i)); + } + TabletV2 *tablet(int i = 0, int iSeat = 0) + { + if (auto *ts = tabletSeat(iSeat)) + return ts->m_tablets.value(i, nullptr); + return nullptr; + } + TabletToolV2 *tabletTool(int i = 0, int iSeat = 0) + { + if (auto *ts = tabletSeat(iSeat)) + return ts->m_tools.value(i, nullptr); + return nullptr; + } + TabletPadV2 *tabletPad(int i = 0, int iSeat = 0) + { + if (auto *ts = tabletSeat(iSeat)) + return ts->m_pads.value(i, nullptr); + return nullptr; + } +}; + +Q_DECLARE_METATYPE(QtWaylandServer::zwp_tablet_tool_v2::type); +Q_DECLARE_METATYPE(QPointingDevice::PointerType); +Q_DECLARE_METATYPE(Qt::MouseButton); + +class tst_tabletv2 : public QObject, private TabletCompositor +{ + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + Q_OBJECT +private slots: + void cleanup(); + void bindsToManager(); + void createsTabletSeat(); + void destroysTablet(); + void destroysTool(); + void destroysPad(); + void proximityEvents(); + void moveEvent(); + void pointerType_data(); + void pointerType(); + void hardwareSerial(); + void buttons_data(); + void buttons(); + void tabletEvents(); +}; + +class ProximityFilter : public QObject { + Q_OBJECT +public: + ProximityFilter() { qApp->installEventFilter(this); } + ~ProximityFilter() override { qDeleteAll(m_events); } + QList<QTabletEvent *> m_events; + + int nextEventIndex = 0; + int numEvents() const { return m_events.size() - nextEventIndex; } + QTabletEvent *popEvent() + { + auto *event = m_events.value(nextEventIndex, nullptr); + if (event) + ++nextEventIndex; + return event; + } + +protected: + bool eventFilter(QObject *object, QEvent *event) override + { + Q_UNUSED(object); + switch (event->type()) { + case QEvent::TabletEnterProximity: + case QEvent::TabletLeaveProximity: { + auto *e = static_cast<QTabletEvent *>(event); + auto *ev = new QTabletEvent(e->type(), e->pointingDevice(), e->position(), e->globalPosition(), + e->pressure(), e->xTilt(), e->yTilt(), + e->tangentialPressure(), e->rotation(), e->z(), + Qt::KeyboardModifier::NoModifier, + e->button(), e->buttons()); + m_events << ev; + break; + } + default: + break; + } + return false; + } +}; + +void tst_tabletv2::cleanup() +{ + exec([&] { + tabletSeat()->removeAll(); + }); + QCOMPOSITOR_COMPARE(get<TabletManagerV2>()->m_tabletSeats.size(), 1); + QCOMPOSITOR_COMPARE(tabletSeat()->m_tablets.size(), 0); + QCOMPOSITOR_COMPARE(tabletSeat()->m_tools.size(), 0); + QCOMPOSITOR_COMPARE(tabletSeat()->m_pads.size(), 0); + + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); +} + +void tst_tabletv2::bindsToManager() +{ + QCOMPOSITOR_TRY_COMPARE(get<TabletManagerV2>()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(get<TabletManagerV2>()->resourceMap().first()->version(), tabletVersion); +} + +void tst_tabletv2::createsTabletSeat() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->resourceMap().contains(client())); + QCOMPOSITOR_TRY_COMPARE(tabletSeat()->resourceMap().value(client())->version(), tabletVersion); + //TODO: Maybe also assert some capability reported though qt APIs? +} + +void tst_tabletv2::destroysTablet() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + }); + QCOMPOSITOR_TRY_VERIFY(tablet()); + + exec([&] { + tablet()->sendRemoved(); + }); + + QCOMPOSITOR_TRY_VERIFY(!tablet()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_tabletsWaitingForDestroy.empty()); +} + +void tst_tabletv2::destroysTool() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTool(); + }); + QCOMPOSITOR_TRY_VERIFY(tabletTool()); + + exec([&] { + tabletTool()->sendRemoved(); + }); + + QCOMPOSITOR_TRY_VERIFY(!tabletTool()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_toolsWaitingForDestroy.empty()); +} + +void tst_tabletv2::destroysPad() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addPad(); + }); + QCOMPOSITOR_TRY_VERIFY(tabletPad()); + + exec([&] { + tabletPad()->sendRemoved(); + }); + + QCOMPOSITOR_TRY_VERIFY(!tabletPad()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_padsWaitingForDestroy.empty()); +} + +void tst_tabletv2::proximityEvents() +{ + ProximityFilter filter; + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *enterEvent = filter.popEvent(); + QCOMPARE(enterEvent->type(), QEvent::TabletEnterProximity); + + exec([&] { + auto *tool = tabletTool(); + tool->sendProximityOut(); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *leaveEvent = filter.popEvent(); + QCOMPARE(leaveEvent->type(), QEvent::TabletLeaveProximity); +} + +class TabletWindow : public QRasterWindow { + Q_OBJECT +public: + ~TabletWindow() override { qDeleteAll(m_events); } + + void tabletEvent(QTabletEvent *e) override + { + m_events << new QTabletEvent(e->type(), e->pointingDevice(), e->position(), e->globalPosition(), + e->pressure(), e->xTilt(), e->yTilt(), + e->tangentialPressure(), e->rotation(), e->z(), + Qt::KeyboardModifier::NoModifier, + e->button(), e->buttons()); + emit tabletEventReceived(m_events.last()); + } + int nextEventIndex = 0; + int numEvents() const { return m_events.size() - nextEventIndex; } + QTabletEvent *popEvent() + { + auto *event = m_events.value(nextEventIndex, nullptr); + if (event) + ++nextEventIndex; + return event; + } + +signals: + void tabletEventReceived(QTabletEvent *event); + +private: + QList<QTabletEvent *> m_events; +}; + +void tst_tabletv2::moveEvent() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + QMargins margins = window.frameMargins(); + tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tool->sendFrame(); + }); + QTRY_VERIFY(window.numEvents()); + QTabletEvent *event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletMove); + QCOMPARE(event->pressure(), 0); + QCOMPARE(event->position(), QPointF(12, 34)); +} + +void tst_tabletv2::pointerType_data() +{ + QTest::addColumn<ToolType>("toolType"); + QTest::addColumn<QPointingDevice::PointerType>("pointerType"); + QTest::addColumn<QInputDevice::DeviceType>("tabletDevice"); + + QTest::newRow("pen") << ToolType::type_pen << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Stylus; + QTest::newRow("eraser") << ToolType::type_eraser << QPointingDevice::PointerType::Eraser << QInputDevice::DeviceType::Stylus; + QTest::newRow("pencil") << ToolType::type_pencil << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Stylus; + QTest::newRow("airbrush") << ToolType::type_airbrush << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Airbrush; + QTest::newRow("brush") << ToolType::type_brush << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Stylus; // TODO: is TabletDevice::Stylus the right thing? + QTest::newRow("lens") << ToolType::type_lens << QPointingDevice::PointerType::Cursor << QInputDevice::DeviceType::Puck; + // TODO: also add tests for FourDMouse and RotationStylus (also need to send capabilities) + + // TODO: should these rather be mapped to touch/mouse events? + QTest::newRow("finger") << ToolType::type_finger << QPointingDevice::PointerType::Unknown << QInputDevice::DeviceType::Unknown; + QTest::newRow("mouse") << ToolType::type_mouse << QPointingDevice::PointerType::Cursor << QInputDevice::DeviceType::Unknown; +} + +void tst_tabletv2::pointerType() +{ + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + QFETCH(ToolType, toolType); + QFETCH(QPointingDevice::PointerType, pointerType); + QFETCH(QInputDevice::DeviceType, tabletDevice); + + ProximityFilter filter; + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(toolType); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + QMargins margins = window.frameMargins(); + tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *event = filter.popEvent(); + QCOMPARE(event->pointerType(), pointerType); + QCOMPARE(event->deviceType(), tabletDevice); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->pointerType(), pointerType); + QCOMPARE(event->deviceType(), tabletDevice); + + exec([&] { + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(filter.numEvents()); + event = filter.popEvent(); + QCOMPARE(event->pointerType(), pointerType); + QCOMPARE(event->deviceType(), tabletDevice); +} + +void tst_tabletv2::hardwareSerial() +{ + ProximityFilter filter; + const QPointingDeviceUniqueId uid = QPointingDeviceUniqueId::fromNumericId(0xbaba15dead15f00d); + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(ToolType::type_pen, uid.numericId()); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + QMargins margins = window.frameMargins(); + tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *event = filter.popEvent(); + QCOMPARE(event->pointingDevice()->uniqueId(), uid); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->pointingDevice()->uniqueId(), uid); + + exec([&] { + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(filter.numEvents()); + event = filter.popEvent(); + QCOMPARE(event->pointingDevice()->uniqueId(), uid); +} + +// As defined in linux/input-event-codes.h +#ifndef BTN_STYLUS +#define BTN_STYLUS 0x14b +#endif +#ifndef BTN_STYLUS2 +#define BTN_STYLUS2 0x14c +#endif + +void tst_tabletv2::buttons_data() +{ + QTest::addColumn<uint>("tabletButton"); + QTest::addColumn<Qt::MouseButton>("mouseButton"); + + QTest::newRow("BTN_STYLUS2") << uint(BTN_STYLUS2) << Qt::MouseButton::RightButton; + QTest::newRow("BTN_STYLUS") << uint(BTN_STYLUS) << Qt::MouseButton::MiddleButton; +} + +void tst_tabletv2::buttons() +{ + QFETCH(uint, tabletButton); + QFETCH(Qt::MouseButton, mouseButton); + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + tabletTool()->sendProximityIn(tablet(), xdgSurface()->m_surface); + QMargins margins = window.frameMargins(); + tabletTool()->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + window.popEvent(); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + tabletTool()->sendButton(tabletButton, true); + tabletTool()->sendFrame(); + tabletTool()->sendButton(tabletButton, false); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + QTabletEvent *event = window.popEvent(); + QCOMPARE(event->buttons(), mouseButton); + + exec([&] { + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); +} + +void tst_tabletv2::tabletEvents() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + const QPointF insideDecorations(window.frameMargins().left(), window.frameMargins().top()); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + // TODO: encapsulate this into a helper function? + tool->sendProximityIn(tablet(), surface); + tool->sendMotion(QPointF(12, 34) + insideDecorations); + tool->sendDown(); + tool->sendPressure(65535); + tool->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + QTabletEvent *event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletPress); + QCOMPARE(event->pressure(), 1.0); + QCOMPARE(event->position(), QPointF(12, 34)); + + // Values we didn't send should be 0 + QCOMPARE(event->rotation(), 0); + QCOMPARE(event->xTilt(), 0); + QCOMPARE(event->yTilt(), 0); + + exec([&] { + tabletTool()->sendMotion(QPointF(45, 56) + insideDecorations); + tabletTool()->sendPressure(65535/2); + tabletTool()->sendRotation(90); + tabletTool()->sendTilt(13, 37); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletMove); + QVERIFY(qAbs(event->pressure() - 0.5) < 0.01); + QVERIFY(qAbs(event->rotation() - 90) < 0.01); + QVERIFY(qAbs(event->xTilt() - 13) < 0.01); + QVERIFY(qAbs(event->yTilt() - 37) < 0.01); + QCOMPARE(event->position(), QPointF(45, 56)); + + // Verify that the values stay the same if we don't update them + exec([&] { + tabletTool()->sendMotion(QPointF(10, 11) + insideDecorations); // Change position only + tabletTool()->sendFrame(); + }); + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletMove); + QVERIFY(qAbs(event->pressure() - 0.5) < 0.01); + QVERIFY(qAbs(event->rotation() - 90) < 0.01); + QVERIFY(qAbs(event->xTilt() - 13) < 0.01); + QVERIFY(qAbs(event->yTilt() - 37) < 0.01); + QCOMPARE(event->position(), QPointF(10, 11)); + + exec([&] { + tabletTool()->sendPressure(0); + tabletTool()->sendUp(); + tabletTool()->sendFrame(); + + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletRelease); + QCOMPARE(event->pressure(), 0); + QCOMPARE(event->position(), QPointF(10, 11)); +} + +QCOMPOSITOR_TEST_MAIN(tst_tabletv2) +#include "tst_tabletv2.moc" diff --git a/tests/auto/client/wl_connect/CMakeLists.txt b/tests/auto/client/wl_connect/CMakeLists.txt new file mode 100644 index 000000000..fff3835bb --- /dev/null +++ b/tests/auto/client/wl_connect/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from wl_connect.pro. + +##################################################################### +## tst_wlconnect Test: +##################################################################### + +qt_internal_add_test(tst_wlconnect + SOURCES + tst_wlconnect.cpp + LIBRARIES + Qt::Gui + Qt::GuiPrivate +) diff --git a/tests/auto/client/wl_connect/tst_wlconnect.cpp b/tests/auto/client/wl_connect/tst_wlconnect.cpp index 661f7ad62..d66d0ad6a 100644 --- a/tests/auto/client/wl_connect/tst_wlconnect.cpp +++ b/tests/auto/client/wl_connect/tst_wlconnect.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QGuiApplication> diff --git a/tests/auto/client/wl_connect/wl_connect.pro b/tests/auto/client/wl_connect/wl_connect.pro deleted file mode 100644 index 28723beee..000000000 --- a/tests/auto/client/wl_connect/wl_connect.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -QT += gui-private testlib - -SOURCES += tst_wlconnect.cpp -TARGET = tst_wlconnect diff --git a/tests/auto/client/xdgdecorationv1/CMakeLists.txt b/tests/auto/client/xdgdecorationv1/CMakeLists.txt new file mode 100644 index 000000000..0f727aaca --- /dev/null +++ b/tests/auto/client/xdgdecorationv1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdgdecorationv1.pro. + +##################################################################### +## tst_xdgdecorationv1 Test: +##################################################################### + +qt_internal_add_test(tst_xdgdecorationv1 + SOURCES + tst_xdgdecorationv1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/xdgdecorationv1/tst_xdgdecorationv1.cpp b/tests/auto/client/xdgdecorationv1/tst_xdgdecorationv1.cpp new file mode 100644 index 000000000..5ee856944 --- /dev/null +++ b/tests/auto/client/xdgdecorationv1/tst_xdgdecorationv1.cpp @@ -0,0 +1,198 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include <qwayland-server-xdg-decoration-unstable-v1.h> + +#include <QtGui/QRasterWindow> +#include <QtGui/QClipboard> +#include <QtCore/private/qcore_unix_p.h> + +#include <fcntl.h> + +using namespace MockCompositor; + +constexpr int xdgDecorationVersion = 1; // protocol VERSION, not the name suffix (_v1) + +class XdgDecorationManagerV1; +class XdgToplevelDecorationV1 : public QObject, public QtWaylandServer::zxdg_toplevel_decoration_v1 +{ + Q_OBJECT +public: + explicit XdgToplevelDecorationV1(XdgDecorationManagerV1 *manager, XdgToplevel *toplevel, int id, int version) + : zxdg_toplevel_decoration_v1(toplevel->resource()->client(), id, version) + , m_manager(manager) + , m_toplevel(toplevel) + { + } + void sendConfigure(mode mode) + { + if (!m_configureSent) { + // Attaching buffers before the configure is a protocol error + QVERIFY(!m_toplevel->surface()->m_pending.buffer); + QVERIFY(!m_toplevel->surface()->m_committed.buffer); + } + send_configure(mode); + m_configureSent = true; + } + void zxdg_toplevel_decoration_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + void zxdg_toplevel_decoration_v1_destroy_resource(Resource *resource) override; + void zxdg_toplevel_decoration_v1_set_mode(Resource *resource, uint32_t mode) override + { + Q_UNUSED(resource); + m_unsetModeRequested = false; + m_requestedMode = XdgToplevelDecorationV1::mode(mode); + } + void zxdg_toplevel_decoration_v1_unset_mode(Resource *resource) override + { + Q_UNUSED(resource); + m_unsetModeRequested = true; + m_requestedMode = mode(0); + } + XdgDecorationManagerV1 *m_manager = nullptr; + XdgToplevel *m_toplevel = nullptr; + mode m_requestedMode = mode(0); + bool m_unsetModeRequested = false; + bool m_configureSent = false; +}; + +class XdgDecorationManagerV1 : public Global, public QtWaylandServer::zxdg_decoration_manager_v1 +{ + Q_OBJECT +public: + explicit XdgDecorationManagerV1(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::zxdg_decoration_manager_v1(compositor->m_display, version) + , m_version(version) + {} + bool isClean() override { return m_decorations.empty(); } + XdgToplevelDecorationV1 *decorationFor(XdgToplevel *toplevel) + { + return m_decorations.value(toplevel, nullptr); + } + + int m_version = 1; // TODO: Remove on libwayland upgrade + QMap<XdgToplevel *, XdgToplevelDecorationV1 *> m_decorations; + +protected: + void zxdg_decoration_manager_v1_destroy(Resource *resource) override + { + //TODO: Should the decorations be destroyed at this point? + wl_resource_destroy(resource->handle); + } + + void zxdg_decoration_manager_v1_get_toplevel_decoration(Resource *resource, uint32_t id, ::wl_resource *toplevelResource) override + { + auto *toplevel = fromResource<XdgToplevel>(toplevelResource); + QVERIFY(toplevel); + QVERIFY(!decorationFor(toplevel)); + + // Attaching buffers before the configure is a protocol error + QVERIFY(!toplevel->surface()->m_pending.buffer); + QVERIFY(!toplevel->surface()->m_committed.buffer); + + m_decorations[toplevel] = new XdgToplevelDecorationV1(this, toplevel, id, resource->version()); + } +}; + +void XdgToplevelDecorationV1::zxdg_toplevel_decoration_v1_destroy_resource(QtWaylandServer::zxdg_toplevel_decoration_v1::Resource *resource) +{ + Q_UNUSED(resource); + int removed = m_manager->m_decorations.remove(m_toplevel); + Q_ASSERT(removed == 1); + delete this; +} + +class XdgDecorationCompositor : public DefaultCompositor { +public: + explicit XdgDecorationCompositor() + { + exec([this] { + m_config.autoConfigure = false; + add<XdgDecorationManagerV1>(xdgDecorationVersion); + }); + } + XdgToplevelDecorationV1 *toplevelDecoration(int i = 0) { + return get<XdgDecorationManagerV1>()->decorationFor(xdgToplevel(i)); + } +}; + +class tst_xdgdecorationv1 : public QObject, private XdgDecorationCompositor +{ + Q_OBJECT +private slots: + void initTestCase(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void clientSidePreferredByCompositor(); + void initialFramelessWindowHint(); + void delayedFramelessWindowHint(); +}; + +void tst_xdgdecorationv1::initTestCase() +{ + if (qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_WINDOWDECORATION")) + QSKIP("This test doesn't make sense when QT_WAYLAND_DISABLE_WINDOWDECORATION is set in the environment"); +} + +void tst_xdgdecorationv1::clientSidePreferredByCompositor() +{ + QRasterWindow window; + window.show(); + QCOMPOSITOR_TRY_COMPARE(get<XdgDecorationManagerV1>()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(get<XdgDecorationManagerV1>()->resourceMap().first()->version(), xdgDecorationVersion); + QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()); // The client creates a toplevel object + + // Check that we don't assume decorations before the server has configured them + QVERIFY(window.frameMargins().isNull()); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_unsetModeRequested); + QVERIFY(window.frameMargins().isNull()); // We're still waiting for a configure + exec([&] { + toplevelDecoration()->sendConfigure(XdgToplevelDecorationV1::mode_client_side); + xdgToplevel()->sendCompleteConfigure(); + }); + QTRY_VERIFY(!window.frameMargins().isNull()); +} + +void tst_xdgdecorationv1::initialFramelessWindowHint() +{ + QRasterWindow window; + window.setFlag(Qt::FramelessWindowHint, true); + window.show(); + QCOMPOSITOR_TRY_COMPARE(get<XdgDecorationManagerV1>()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&]{ + xdgToplevel()->sendCompleteConfigure(); + }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + + // The client should not have create a decoration object, because that allows the compositor + // to override our decision and add server side decorations to our window. + QCOMPOSITOR_TRY_VERIFY(!toplevelDecoration()); +} + +void tst_xdgdecorationv1::delayedFramelessWindowHint() +{ + QRasterWindow window; + window.show(); + QCOMPOSITOR_TRY_COMPARE(get<XdgDecorationManagerV1>()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&]{ + xdgToplevel()->sendCompleteConfigure(); + }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()); + + window.setFlag(Qt::FramelessWindowHint, true); + + // The client should now destroy the decoration object, so the compositor is no longer + // able to force window decorations + QCOMPOSITOR_TRY_VERIFY(!toplevelDecoration()); +} + +QCOMPOSITOR_TEST_MAIN(tst_xdgdecorationv1) +#include "tst_xdgdecorationv1.moc" diff --git a/tests/auto/client/xdgoutput/CMakeLists.txt b/tests/auto/client/xdgoutput/CMakeLists.txt new file mode 100644 index 000000000..123a78f8e --- /dev/null +++ b/tests/auto/client/xdgoutput/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdgoutput.pro. + +##################################################################### +## tst_xdgoutput Test: +##################################################################### + +qt_internal_add_test(tst_xdgoutput + SOURCES + tst_xdgoutput.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/xdgoutput/tst_xdgoutput.cpp b/tests/auto/client/xdgoutput/tst_xdgoutput.cpp new file mode 100644 index 000000000..d021e3883 --- /dev/null +++ b/tests/auto/client/xdgoutput/tst_xdgoutput.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "xdgoutputv1.h" +#include "mockcompositor.h" + +#include <QtGui/QRasterWindow> +#include <QtGui/QScreen> + +using namespace MockCompositor; + +class XdgOutputV1Compositor : public DefaultCompositor { +public: + explicit XdgOutputV1Compositor() + { + exec([this] { + int version = 3; // version 3 of of unstable-v1 + add<XdgOutputManagerV1>(version); + }); + } + XdgOutputV1 *xdgOutput(int i = 0) { return get<XdgOutputManagerV1>()->getXdgOutput(output(i)); } +}; + +class tst_xdgoutput : public QObject, private XdgOutputV1Compositor +{ + Q_OBJECT +private slots: + void cleanup(); + void primaryScreen(); + void overrideGeometry(); + void changeGeometry(); + void outputCreateEnterRace(); +}; + +void tst_xdgoutput::cleanup() +{ + QCOMPOSITOR_COMPARE(getAll<Output>().size(), 1); // Only the default output should be left + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); +} + +void tst_xdgoutput::primaryScreen() +{ + // Verify that the client has bound to the global + QCOMPOSITOR_TRY_COMPARE(get<XdgOutputManagerV1>()->resourceMap().size(), 1); + exec([&] { + auto *resource = xdgOutput()->resourceMap().value(client()); + QCOMPARE(resource->version(), 3); + QCOMPARE(xdgOutput()->m_logicalGeometry.size(), QSize(1920, 1080)); + }); + auto *s = QGuiApplication::primaryScreen(); + QTRY_COMPARE(s->size(), QSize(1920, 1080)); + QTRY_COMPARE(s->geometry().topLeft(), QPoint(0, 0)); + QTRY_COMPARE(s->name(), QString("WL-1")); +} + +void tst_xdgoutput::overrideGeometry() +{ + exec([&] { + auto *output = add<Output>(); + auto *xdgOutput = get<XdgOutputManagerV1>()->getXdgOutput(output); + xdgOutput->m_logicalGeometry = QRect(10, 20, 800, 1200); + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + auto *s = QGuiApplication::screens()[1]; + + QTRY_COMPARE(s->size(), QSize(800, 1200)); + QTRY_COMPARE(s->geometry().topLeft(), QPoint(10, 20)); + + exec([&] { remove(output(1)); }); +} + +void tst_xdgoutput::changeGeometry() +{ + auto *xdgOutput = exec([&] { + auto *output = add<Output>(); + auto *xdgOutput = get<XdgOutputManagerV1>()->getXdgOutput(output); + xdgOutput->m_logicalGeometry = QRect(10, 20, 800, 1200); + return xdgOutput; + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + auto *screen = QGuiApplication::screens()[1]; + QTRY_COMPARE(screen->size(), QSize(800, 1200)); + + exec([&] { + xdgOutput->sendLogicalSize(QSize(1024, 768)); + }); + + // Now we want to check that the client doesn't apply the size immediately, but waits for the + // done event. If we TRY_COMPARE immediately, we risk that the client just hasn't handled the + // logical_size request yet, so we add a screen and verify it on the client side just to give + // the client a chance to mess up. + exec([&] { add<Output>(); }); + QTRY_COMPARE(QGuiApplication::screens().size(), 3); + exec([&] { remove(output(2)); }); + + // The logical_size event should have been handled by now, but state should not have been applied yet. + QTRY_COMPARE(screen->size(), QSize(800, 1200)); + + exec([&] { + xdgOutput->m_output->sendDone(); + }); + + // Finally, the size should change + QTRY_COMPARE(screen->size(), QSize(1024, 768)); + + exec([&] { remove(output(1)); }); +} + +void tst_xdgoutput::outputCreateEnterRace() +{ + m_config.autoConfigure = true; + m_config.autoEnter = false; + QRasterWindow window; + QSignalSpy screenChanged(&window, &QWindow::screenChanged); + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { xdgToplevel()->surface()->sendEnter(output(0));}); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + auto *out = exec([&] { + return add<Output>(); + }); + + // In Compositor Thread + connect(out, &Output::outputBound, this, [this](QtWaylandServer::wl_output::Resource *resource){ + auto surface = xdgToplevel()->surface(); + surface->sendLeave(output(0)); + surface->QtWaylandServer::wl_surface::send_enter(surface->resource()->handle, resource->handle); + }, Qt::DirectConnection); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(window.screen(), QGuiApplication::screens()[1]); + + exec([&] { remove(out); }); + m_config.autoConfigure = false; + m_config.autoEnter = true; +} + +QCOMPOSITOR_TEST_MAIN(tst_xdgoutput) +#include "tst_xdgoutput.moc" diff --git a/tests/auto/client/xdgshell/CMakeLists.txt b/tests/auto/client/xdgshell/CMakeLists.txt new file mode 100644 index 000000000..fa7590249 --- /dev/null +++ b/tests/auto/client/xdgshell/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdgshell.pro. + +##################################################################### +## tst_xdgshell Test: +##################################################################### + +qt_internal_add_test(tst_xdgshell + SOURCES + tst_xdgshell.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp new file mode 100644 index 000000000..1dc57e280 --- /dev/null +++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -0,0 +1,809 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include <QtGui/QRasterWindow> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtWaylandClient/private/wayland-wayland-client-protocol.h> +#include <QtWaylandClient/private/qwaylandwindow_p.h> + +using namespace MockCompositor; + +class tst_xdgshell : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void showMinimized(); + void basicConfigure(); + void configureSize(); + void configureStates(); + void configureBounds(); + void popup(); + void tooltipOnPopup(); + void tooltipAndSiblingPopup(); + void switchPopups(); + void hidePopupParent(); + void pongs(); + void minMaxSize_data(); + void minMaxSize(); + void windowGeometry(); + void foreignSurface(); + void nativeResources(); + void suspended(); + void initiallySuspended(); + void modality(); +}; + +void tst_xdgshell::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_xdgshell::showMinimized() +{ + // On xdg-shell there's really no way for the compositor to tell the window if it's minimized + // There are wl_surface.enter events and so on, but there's really no way to differentiate + // between a window preview and an unminimized window. + QWindow window; + window.showMinimized(); + QCOMPARE(window.windowStates(), Qt::WindowMinimized); // should return minimized until + QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged + + // Make sure the window on the compositor side is/was created here, and not after the test + // finishes, as that may mess up for later tests. + QCOMPOSITOR_TRY_VERIFY(xdgSurface()); + QVERIFY(!window.isExposed()); +} + +void tst_xdgshell::basicConfigure() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + + QTRY_VERIFY(window.isVisible()); + // The window should not be exposed before the first xdg_surface configure event + QTRY_VERIFY(!window.isExposed()); + + exec([&] { + xdgToplevel()->sendConfigure({0, 0}, {}); // Let the window decide the size + }); + + // Nothing should happen before the *xdg_surface* configure + QTRY_VERIFY(!window.isExposed()); //Window should not be exposed before the first configure event + QVERIFY(configureSpy.isEmpty()); + + const uint serial = exec([&] { return nextSerial(); }); + + exec([&] { + xdgSurface()->sendConfigure(serial); + }); + + // Finally, we're exposed + QTRY_VERIFY(window.isExposed()); + + // The client is now going to ack the configure + QTRY_COMPARE(configureSpy.size(), 1); + QCOMPARE(configureSpy.takeFirst().at(0).toUInt(), serial); + + // And attach a buffer + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), window.frameGeometry().size()); + }); +} + +void tst_xdgshell::configureSize() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + + const QSize configureSize(60, 40); + + exec([&] { + xdgToplevel()->sendCompleteConfigure(configureSize); + }); + + QTRY_COMPARE(configureSpy.size(), 1); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), configureSize); + }); +} + +void tst_xdgshell::configureStates() +{ + QVERIFY(qputenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", "0")); + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + const QSize windowedSize(320, 240); + const uint windowedSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure(windowedSize, { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, windowedSerial); + QCOMPARE(window.visibility(), QWindow::Windowed); + QCOMPARE(window.windowStates(), Qt::WindowNoState); + QCOMPARE(window.frameGeometry().size(), windowedSize); + // Toplevel windows don't know their position on xdg-shell +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + // window.windowstate() is driven by keyboard focus, however for decorations we want to follow + // XDGShell this is internal to QtWayland so it is queried directly + auto waylandWindow = static_cast<QtWaylandClient::QWaylandWindow *>(window.handle()); + Q_ASSERT(waylandWindow); + QTRY_VERIFY(waylandWindow->windowStates().testFlag( + Qt::WindowActive)); // Just make sure it eventually get's set correctly + + const QSize screenSize(640, 480); + const uint maximizedSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_maximized }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, maximizedSerial); + QCOMPARE(window.visibility(), QWindow::Maximized); + QCOMPARE(window.windowStates(), Qt::WindowMaximized); + QCOMPARE(window.frameGeometry().size(), screenSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + const uint fullscreenSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_fullscreen }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, fullscreenSerial); + QCOMPARE(window.visibility(), QWindow::FullScreen); + QCOMPARE(window.windowStates(), Qt::WindowFullScreen); + QCOMPARE(window.frameGeometry().size(), screenSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + // The window should remember its original size + const uint restoreSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure({0, 0}, { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, restoreSerial); + QCOMPARE(window.visibility(), QWindow::Windowed); + QCOMPARE(window.windowStates(), Qt::WindowNoState); + QCOMPARE(window.frameGeometry().size(), windowedSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + QVERIFY(qunsetenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT")); +} + +void tst_xdgshell::configureBounds() +{ + QRasterWindow window; + window.resize(1280, 1024); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + // Take xdg_toplevel.configure_bounds into account only if the configure event has 0x0 size. + const uint serial1 = exec([&] { + xdgToplevel()->sendConfigureBounds(QSize(800, 600)); + return xdgToplevel()->sendCompleteConfigure(QSize(0, 0), { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial1); + QCOMPARE(window.frameGeometry().size(), QSize(800, 600)); + + // Window size in xdg_toplevel configure events takes precedence over the configure bounds. + const uint serial2 = exec([&] { + xdgToplevel()->sendConfigureBounds(QSize(800, 600)); + return xdgToplevel()->sendCompleteConfigure(QSize(1600, 900), { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial2); + QCOMPARE(window.frameGeometry().size(), QSize(1600, 900)); +} + +void tst_xdgshell::popup() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + QRasterWindow::mousePressEvent(event); + m_popup.reset(new QRasterWindow); + m_popup->setTransientParent(this); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + QScopedPointer<QRasterWindow> m_popup; + }; + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy toplevelConfigureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QTRY_COMPARE(toplevelConfigureSpy.size(), 1); + + uint clickSerial = exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + uint serial = p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + return serial; + }); + + QTRY_VERIFY(window.m_popup); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + QSignalSpy popupConfigureSpy(exec([&] { return xdgPopup()->m_xdgSurface; }), &XdgSurface::configureCommitted); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + QCOMPOSITOR_TRY_COMPARE(xdgPopup()->m_grabSerial, clickSerial); + + QRasterWindow *popup = window.m_popup.get(); + QVERIFY(!popup->isExposed()); // wait for configure + + QRect rect1 = QRect(100, 100, 100, 100); + exec([&] { xdgPopup()->sendConfigure(rect1); }); + + // Nothing should happen before the *xdg_surface* configure + QTRY_VERIFY(!popup->isExposed()); // Popup shouldn't be exposed before the first configure event + QVERIFY(popupConfigureSpy.isEmpty()); + + const uint configureSerial = exec([&] { + return xdgPopup()->m_xdgSurface->sendConfigure(); + }); + + // Finally, we're exposed + QTRY_VERIFY(popup->isExposed()); + + // The client is now going to ack the configure + QTRY_COMPARE(popupConfigureSpy.size(), 1); + QCOMPARE(popupConfigureSpy.takeFirst().at(0).toUInt(), configureSerial); + QCOMPARE(popup->geometry(), rect1); + + QRect rect2 = QRect(50, 50, 150, 150); + exec([&] { xdgPopup()->sendConfigure(rect2); }); + + const uint configureSerial2 = exec([&] { + return xdgPopup()->m_xdgSurface->sendConfigure(); + }); + + QTRY_COMPARE(popupConfigureSpy.size(), 1); + QCOMPARE(popupConfigureSpy.takeFirst().at(0).toUInt(), configureSerial2); + QCOMPARE(popup->geometry(), rect2); + + // And attach a buffer + exec([&] { + Buffer *buffer = xdgPopup()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), popup->frameGeometry().size()); + }); +} + +void tst_xdgshell::tooltipOnPopup() +{ + class Popup : public QRasterWindow { + public: + explicit Popup(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::Popup); + resize(100, 100); + show(); + } + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_tooltip = new QRasterWindow; + m_tooltip->setTransientParent(this); + m_tooltip->setFlags(Qt::ToolTip); + m_tooltip->resize(100, 100); + m_tooltip->show(); + } + QWindow *m_tooltip = nullptr; + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_popup = new Popup(this); + } + Popup *m_popup = nullptr; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + + exec([&] { + auto *surface = xdgPopup()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)); + exec([&] { xdgPopup(1)->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup(1)->m_grabbed); + + // Close the middle popup (according protocol this should also destroy any nested popups) + window.m_popup->close(); + + // Close order is verified in XdgSurface::xdg_surface_destroy + + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); +} + +void tst_xdgshell::tooltipAndSiblingPopup() +{ + class ToolTip : public QRasterWindow { + public: + explicit ToolTip(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::ToolTip); + resize(100, 100); + show(); + } + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_popup = new QRasterWindow; + m_popup->setTransientParent(transientParent()); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + + QRasterWindow *m_popup = nullptr; + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_tooltip = new ToolTip(this); + } + ToolTip *m_tooltip = nullptr; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup()->m_grabbed); + + exec([&] { + auto *surface = xdgPopup()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)); + exec([&] { xdgPopup(1)->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_grabbed); + + // Close the middle tooltip (it should not close the sibling popup) + window.m_tooltip->close(); + + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + // Verify the remaining xdg surface is a grab popup.. + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)->m_grabbed); + + window.m_tooltip->m_popup->close(); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); +} + +// QTBUG-65680 +void tst_xdgshell::switchPopups() +{ + class Popup : public QRasterWindow { + public: + explicit Popup(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::Popup); + resize(10, 10); + show(); + } + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + if (!m_popups.empty()) + m_popups.last()->setVisible(false); + } + // The bug this checks for, is the case where there is a flushWindowSystemEvents() call + // somewhere within setVisible(false) before the grab has been released. + // This leads to the the release event below—including its show() call—to be handled + // At a time where there is still an active grab, making it illegal for the new popup to + // grab. + void mouseReleaseEvent(QMouseEvent *event) override { + QRasterWindow::mouseReleaseEvent(event); + m_popups << new Popup(this); + } + ~Window() override { qDeleteAll(m_popups); } + QList<Popup *> m_popups; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + + QSignalSpy firstDestroyed(exec([&] { return xdgPopup(); }), &XdgPopup::destroyRequested); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + // The client will now hide one popup and then show another + firstDestroyed.wait(); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup(1)); + + // Verify we still grabbed in case the client has checks to avoid illegal grabs + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + + // Sometimes the popup will select another parent if one is illegal at the time it tries to + // grab. So we verify we got the intended parent on the compositor side. + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_parentXdgSurface == xdgToplevel()->m_xdgSurface); + + // For good measure just check that configuring works as usual + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); +} + +void tst_xdgshell::hidePopupParent() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + QRasterWindow::mousePressEvent(event); + m_popup.reset(new QRasterWindow); + m_popup->setTransientParent(this); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + QScopedPointer<QRasterWindow> m_popup; + }; + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { + xdgPopup()->sendConfigure(QRect(100, 100, 100, 100)); + xdgPopup()->m_xdgSurface->sendConfigure(); + }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + + window.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgToplevel()); +} + +void tst_xdgshell::pongs() +{ + // Create and show a window to trigger shell integration initialzation, + // otherwise we don't have anything to send ping events to. + QRasterWindow window; + window.resize(200, 200); + window.show(); + + // Verify that the client has bound to the global + QCOMPOSITOR_TRY_COMPARE(get<XdgWmBase>()->resourceMap().size(), 1); + + QSignalSpy pongSpy(exec([&] { return get<XdgWmBase>(); }), &XdgWmBase::pong); + const uint serial = exec([&] { return nextSerial(); }); + exec([&] { + auto *base = get<XdgWmBase>(); + wl_resource *resource = base->resourceMap().first()->handle; + base->send_ping(resource, serial); + }); + QTRY_COMPARE(pongSpy.size(), 1); + QCOMPARE(pongSpy.first().at(0).toUInt(), serial); +} + +void tst_xdgshell::minMaxSize_data() +{ + QTest::addColumn<QSize>("initialMinSize"); + QTest::addColumn<QSize>("initialMaxSize"); + QTest::addColumn<QSize>("nextMinSize"); + QTest::addColumn<QSize>("nextMaxSize"); + QTest::addColumn<QSize>("expectedInitialMinSize"); + QTest::addColumn<QSize>("expectedInitialMaxSize"); + QTest::addColumn<QSize>("expectedNextMinSize"); + QTest::addColumn<QSize>("expectedNextMaxSize"); + + QTest::newRow("onlyMinSize") << QSize(50, 60) << QSize() << QSize(500, 600) << QSize() + << QSize(50, 60) << QSize(0, 0) << QSize(500, 600) << QSize(0, 0); + + QTest::newRow("onlyMaxSize") << QSize() << QSize(70, 80) << QSize() << QSize(700, 800) + << QSize(0,0 ) << QSize(70, 80) << QSize(0, 0) << QSize(700, 800); + + QTest::newRow("maxIsSentAsZero") << QSize() << QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX) << QSize() << QSize() + << QSize(0,0 ) << QSize(0, 0) << QSize(0, 0) << QSize(0, 0); + + + QTest::newRow("fullHints") << QSize(50, 60) << QSize(700, 800) << QSize(500, 600) << QSize(710, 810) + << QSize(50, 60) << QSize(700, 800) << QSize(500, 600) << QSize(710, 810); + + // setting a minimum above the maximum is not allowed, we should no-op + QTest::newRow("invalidResize") << QSize(50, 60) << QSize(100, 100) << QSize(500, 600) << QSize(100, 100) + << QSize(50, 60) << QSize(100, 100) << QSize(50, 60) << QSize(100, 100);} + +void tst_xdgshell::minMaxSize() +{ + QFETCH(QSize, initialMinSize); + QFETCH(QSize, initialMaxSize); + + QFETCH(QSize, expectedInitialMinSize); + QFETCH(QSize, expectedInitialMaxSize); + + QRasterWindow window; + if (initialMinSize.isValid()) + window.setMinimumSize(initialMinSize); + if (initialMaxSize.isValid()) + window.setMaximumSize(initialMaxSize); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + // we don't roundtrip with our configuration the initial commit should be correct + + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, expectedInitialMinSize); + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, expectedInitialMaxSize); + + QFETCH(QSize, nextMinSize); + QFETCH(QSize, expectedNextMinSize); + window.setMinimumSize(nextMinSize); + window.update(); + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, expectedNextMinSize); + + QFETCH(QSize, nextMaxSize); + QFETCH(QSize, expectedNextMaxSize); + + window.setMaximumSize(nextMaxSize); + window.update(); + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, expectedNextMaxSize); +} + +void tst_xdgshell::windowGeometry() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + QSize marginsSize; + marginsSize.setWidth(window.frameMargins().left() + window.frameMargins().right()); + marginsSize.setHeight(window.frameMargins().top() + window.frameMargins().bottom()); + + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committed.windowGeometry, QRect(QPoint(0, 0), QSize(400, 320) + marginsSize)); + + window.resize(800, 600); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committed.windowGeometry, QRect(QPoint(0, 0), QSize(800, 600) + marginsSize)); +} + +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.size(), 1); + + wl_surface_destroy(foreignSurface); +} + +void tst_xdgshell::nativeResources() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + auto *ni = QGuiApplication::platformNativeInterface(); + auto *xdg_surface_proxy = static_cast<::wl_proxy *>(ni->nativeResourceForWindow("xdg_surface", &window)); + QCOMPARE(wl_proxy_get_class(xdg_surface_proxy), "xdg_surface"); + + auto *xdg_toplevel_proxy = static_cast<::wl_proxy *>(ni->nativeResourceForWindow("xdg_toplevel", &window)); + QCOMPARE(wl_proxy_get_class(xdg_toplevel_proxy), "xdg_toplevel"); + + auto *xdg_popup_proxy = static_cast<::wl_proxy *>(ni->nativeResourceForWindow("xdg_popup", &window)); + QCOMPARE(xdg_popup_proxy, nullptr); +} + +void tst_xdgshell::suspended() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QVERIFY(!window.isExposed()); // not exposed until we're configured + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + exec([=] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + QTRY_VERIFY(window.isExposed()); + + exec([=] { xdgToplevel()->sendCompleteConfigure(QSize(), {XdgToplevel::state_suspended}); }); + QTRY_VERIFY(!window.isExposed()); + + exec([=] { xdgToplevel()->sendCompleteConfigure(QSize(), {}); }); + QTRY_VERIFY(window.isExposed()); +} + +void tst_xdgshell::initiallySuspended() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QVERIFY(!window.isExposed()); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([=] { xdgToplevel()->sendCompleteConfigure(QSize(), {XdgToplevel::state_suspended}); }); + QVERIFY(!window.isExposed()); +} + +void tst_xdgshell::modality() +{ + QRasterWindow parent; + parent.resize(400, 320); + parent.show(); + + QRasterWindow child; + child.resize(400, 320); + child.setTransientParent(&parent); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1)); + QCOMPOSITOR_VERIFY(!xdgDialog()); + + child.hide(); + child.setModality(Qt::WindowModal); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgDialog()); + QCOMPOSITOR_VERIFY(xdgDialog()->modal); + + child.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); + + child.setModality(Qt::ApplicationModal); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgDialog()); + QCOMPOSITOR_VERIFY(xdgDialog()->modal); + + child.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); + + child.show(); + child.setModality(Qt::NonModal); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); +} + +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 deleted file mode 100644 index 3c822325b..000000000 --- a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp +++ /dev/null @@ -1,442 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 <QBackingStore> -#include <QPainter> -#include <QScreen> -#include <QWindow> -#include <QMimeData> -#include <QPixmap> -#include <QDrag> - -#include <QtTest/QtTest> - -static const QSize screenSize(1600, 1200); - -class TestWindow : public QWindow -{ - Q_OBJECT -public: - TestWindow() - { - setSurfaceType(QSurface::RasterSurface); - setGeometry(0, 0, 32, 32); - create(); - } - - bool event(QEvent *event) override - { - if (event->type() == QEvent::WindowStateChange) - emit windowStateChangeEventReceived(static_cast<QWindowStateChangeEvent *>(event)->oldState()); - return QWindow::event(event); - } - - void exposeEvent(QExposeEvent *event) override - { - ++exposeEventCount; - QWindow::exposeEvent(event); - } - - int exposeEventCount = 0; - -signals: - void windowStateChangeEventReceived(uint oldState); -}; - -class tst_WaylandClientXdgShellV6 : public QObject -{ - Q_OBJECT -public: - tst_WaylandClientXdgShellV6(MockCompositor *c) - : m_compositor(c) - { - qRegisterMetaType<Qt::WindowState>(); - QSocketNotifier *notifier = new QSocketNotifier(m_compositor->waylandFileDescriptor(), QSocketNotifier::Read, this); - connect(notifier, &QSocketNotifier::activated, this, &tst_WaylandClientXdgShellV6::processWaylandEvents); - // connect to the event dispatcher to make sure to flush out the outgoing message queue - connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &tst_WaylandClientXdgShellV6::processWaylandEvents); - connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &tst_WaylandClientXdgShellV6::processWaylandEvents); - } - -public slots: - void processWaylandEvents() - { - m_compositor->processWaylandEvents(); - } - - void cleanup() - { - // make sure the surfaces from the last test are properly cleaned up - // and don't show up as false positives in the next test - QTRY_VERIFY(!m_compositor->surface()); - QTRY_VERIFY(!m_compositor->xdgToplevelV6()); - } - -private slots: - void createDestroyWindow(); - void configure(); - void showMinimized(); - void setMinimized(); - void unsetMaximized(); - void focusWindowFollowsConfigure(); - void windowStateChangedEvents(); - void windowGeometrySimple(); - void windowGeometryFixed(); - void flushUnconfiguredXdgSurface(); - void dontSpamExposeEvents(); - -private: - MockCompositor *m_compositor = nullptr; -}; - -void tst_WaylandClientXdgShellV6::createDestroyWindow() -{ - TestWindow window; - window.show(); - - QTRY_VERIFY(m_compositor->surface()); - - window.destroy(); - QTRY_VERIFY(!m_compositor->surface()); -} - -void tst_WaylandClientXdgShellV6::configure() -{ - QSharedPointer<MockOutput> output; - QTRY_VERIFY(output = m_compositor->output()); - - TestWindow window; - window.show(); - - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = m_compositor->surface()); - - m_compositor->processWaylandEvents(); - QTRY_VERIFY(window.isVisible()); - QTRY_VERIFY(!window.isExposed()); //Window should not be exposed before the first configure event - - //TODO: according to xdg-shell protocol, a buffer should not be attached to a the surface - //until it's configured. Ensure this in the test! - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - - const QSize newSize(123, 456); - m_compositor->sendXdgToplevelV6Configure(toplevel, newSize); - QTRY_VERIFY(window.isExposed()); - QTRY_COMPARE(window.visibility(), QWindow::Windowed); - QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); - QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), newSize)); - - m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED, ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED }); - QTRY_COMPARE(window.visibility(), QWindow::Maximized); - QTRY_COMPARE(window.windowStates(), Qt::WindowMaximized); - QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), screenSize)); - - m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED, ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN }); - QTRY_COMPARE(window.visibility(), QWindow::FullScreen); - QTRY_COMPARE(window.windowStates(), Qt::WindowFullScreen); - QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), screenSize)); - - //The window should remember it's original size - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED }); - QTRY_COMPARE(window.visibility(), QWindow::Windowed); - QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); - QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), newSize)); -} - -void tst_WaylandClientXdgShellV6::showMinimized() -{ - // On xdg-shell v6 there's really no way for the compositor to tell the window if it's minimized - // There are wl_surface.enter events and so on, but there's really no way to differentiate - // between a window preview and an unminimized window. - TestWindow window; - window.showMinimized(); - QCOMPARE(window.windowStates(), Qt::WindowMinimized); // should return minimized until - QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged - - // Make sure the window on the compositor side is/was created here, and not after the test - // finishes, as that may mess up for later tests. - QTRY_VERIFY(m_compositor->xdgToplevelV6()); -} - -void tst_WaylandClientXdgShellV6::setMinimized() -{ - TestWindow window; - window.show(); - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - - m_compositor->sendXdgToplevelV6Configure(toplevel); - QTRY_COMPARE(window.visibility(), QWindow::Windowed); - QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); - - QSignalSpy setMinimizedSpy(toplevel.data(), SIGNAL(setMinimizedRequested())); - QSignalSpy windowStateChangeSpy(&window, SIGNAL(windowStateChangeEventReceived(uint))); - - window.setVisibility(QWindow::Minimized); - QCOMPARE(window.visibility(), QWindow::Minimized); - QCOMPARE(window.windowStates(), Qt::WindowMinimized); - QTRY_COMPARE(setMinimizedSpy.count(), 1); - { - QTRY_VERIFY(windowStateChangeSpy.count() > 0); - Qt::WindowStates oldStates(windowStateChangeSpy.takeFirst().at(0).toUInt()); - QCOMPARE(oldStates, Qt::WindowNoState); - } - - // In the meantime the compositor may minimize, do nothing or reshow the window without - // telling us. - - QTRY_COMPARE(window.visibility(), QWindow::Windowed); // verify that we don't know anything - QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); - { - QTRY_COMPARE(windowStateChangeSpy.count(), 1); - Qt::WindowStates oldStates(windowStateChangeSpy.takeFirst().at(0).toUInt()); - QCOMPARE(oldStates, Qt::WindowNoState); // because the window never was minimized - } - - // Setting visibility again should send another set_minimized request - window.setVisibility(QWindow::Minimized); - QTRY_COMPARE(setMinimizedSpy.count(), 2); -} - -void tst_WaylandClientXdgShellV6::unsetMaximized() -{ - TestWindow window; - window.show(); - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - - QSignalSpy unsetMaximizedSpy(toplevel.data(), SIGNAL(unsetMaximizedRequested())); - - QSignalSpy windowStateChangedSpy(&window, SIGNAL(windowStateChanged(Qt::WindowState))); - - m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED }); - - QTRY_COMPARE(windowStateChangedSpy.count(), 1); - QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowMaximized); - - window.setWindowStates(Qt::WindowNoState); - - QTRY_COMPARE(unsetMaximizedSpy.count(), 1); - QTRY_COMPARE(windowStateChangedSpy.count(), 1); - QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowNoState); - - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {}); - - QTRY_COMPARE(windowStateChangedSpy.count(), 1); - QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowNoState); -} - -void tst_WaylandClientXdgShellV6::focusWindowFollowsConfigure() -{ - TestWindow window; - window.show(); - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - QTRY_VERIFY(!window.isActive()); - - QSignalSpy windowStateChangeSpy(&window, SIGNAL(windowStateChangeEventReceived(uint))); - - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED }); - QTRY_VERIFY(window.isActive()); - - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {}); - QTRY_VERIFY(!window.isActive()); -} - -void tst_WaylandClientXdgShellV6::windowStateChangedEvents() -{ - TestWindow window; - window.show(); - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - - QSignalSpy eventSpy(&window, SIGNAL(windowStateChangeEventReceived(uint))); - QSignalSpy signalSpy(&window, SIGNAL(windowStateChanged(Qt::WindowState))); - - m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED }); - - QTRY_COMPARE(window.windowStates(), Qt::WindowMaximized); - QTRY_COMPARE(window.windowState(), Qt::WindowMaximized); - { - QTRY_COMPARE(eventSpy.count(), 1); - Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt()); - QCOMPARE(oldStates, Qt::WindowNoState); - - QTRY_COMPARE(signalSpy.count(), 1); - uint newState = signalSpy.takeFirst().at(0).toUInt(); - QCOMPARE(newState, Qt::WindowMaximized); - } - - m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN }); - - QTRY_COMPARE(window.windowStates(), Qt::WindowFullScreen); - QTRY_COMPARE(window.windowState(), Qt::WindowFullScreen); - { - QTRY_COMPARE(eventSpy.count(), 1); - Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt()); - QCOMPARE(oldStates, Qt::WindowMaximized); - - QTRY_COMPARE(signalSpy.count(), 1); - uint newState = signalSpy.takeFirst().at(0).toUInt(); - QCOMPARE(newState, Qt::WindowFullScreen); - } - - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {}); - - QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); - QTRY_COMPARE(window.windowState(), Qt::WindowNoState); - { - QTRY_COMPARE(eventSpy.count(), 1); - Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt()); - QCOMPARE(oldStates, Qt::WindowFullScreen); - - QTRY_COMPARE(signalSpy.count(), 1); - uint newState = signalSpy.takeFirst().at(0).toUInt(); - QCOMPARE(newState, Qt::WindowNoState); - } -} - -void tst_WaylandClientXdgShellV6::windowGeometrySimple() -{ - QWindow window; - window.show(); - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - QSignalSpy geometrySpy(toplevel.data(), SIGNAL(windowGeometryRequested(QRect))); - - m_compositor->sendXdgToplevelV6Configure(toplevel); - QTRY_COMPARE(geometrySpy.count(), 1); - QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), window.frameGeometry().size()); - - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(123, 456)); - QTRY_COMPARE(geometrySpy.count(), 1); - QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), QSize(123, 456)); -} - -void tst_WaylandClientXdgShellV6::windowGeometryFixed() -{ - QWindow window; - window.resize(QSize(1337, 137)); - window.setMaximumSize(window.size()); - window.setMinimumSize(window.size()); - window.show(); - - QSharedPointer<MockXdgToplevelV6> toplevel; - QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6()); - QSignalSpy geometrySpy(toplevel.data(), SIGNAL(windowGeometryRequested(QRect))); - - m_compositor->sendXdgToplevelV6Configure(toplevel); - QTRY_COMPARE(geometrySpy.count(), 1); - QRect initialWindowGeometry = geometrySpy.takeFirst().at(0).toRect(); - QCOMPARE(initialWindowGeometry.size(), window.frameGeometry().size()); - - m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(123, 456)); - QTRY_COMPARE(geometrySpy.count(), 1); - // Configuring the window should not change the window geometry - QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), initialWindowGeometry.size()); -} - -void tst_WaylandClientXdgShellV6::flushUnconfiguredXdgSurface() -{ - TestWindow window; - window.show(); - - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = m_compositor->surface()); - - // Paint and flush some magenta - QBackingStore backingStore(&window); - QRect rect(QPoint(), window.size()); - backingStore.resize(rect.size()); - backingStore.beginPaint(rect); - QColor color = Qt::magenta; - QPainter p(backingStore.paintDevice()); - p.fillRect(rect, color); - p.end(); - backingStore.endPaint(); - backingStore.flush(rect); - - // We're not allowed to send buffer on this surface since it isn't yet configured. - // So, from the compositor's point of view there should be no buffer data yet. - m_compositor->processWaylandEvents(); - QVERIFY(surface->image.isNull()); - QVERIFY(!window.isExposed()); - - // Finally sending the configure should trigger an attach and commit with the - // right buffer. - m_compositor->sendShellSurfaceConfigure(surface); - QTRY_COMPARE(surface->image.size(), window.frameGeometry().size()); - QTRY_COMPARE(surface->image.pixel(window.frameMargins().left(), window.frameMargins().top()), color.rgba()); - QVERIFY(window.isExposed()); -} - -void tst_WaylandClientXdgShellV6::dontSpamExposeEvents() -{ - TestWindow window; - window.show(); - - QSharedPointer<MockSurface> surface; - QTRY_VERIFY(surface = m_compositor->surface()); - QTRY_VERIFY(window.exposeEventCount == 0); - - m_compositor->sendShellSurfaceConfigure(surface); - QTRY_VERIFY(window.isExposed()); - QTRY_VERIFY(window.exposeEventCount == 1); -} - -int main(int argc, char **argv) -{ - setenv("XDG_RUNTIME_DIR", ".", 1); - setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin - setenv("QT_WAYLAND_SHELL_INTEGRATION", "xdg-shell-v6", 1); - - // wayland-egl hangs in the test setup when we try to initialize. Until it gets - // figured out, avoid clientBufferIntegration() from being called in - // QWaylandWindow::createDecorations(). - setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); - - MockCompositor compositor; - compositor.setOutputMode(screenSize); - - QGuiApplication app(argc, argv); - compositor.applicationInitialized(); - - tst_WaylandClientXdgShellV6 tc(&compositor); - return QTest::qExec(&tc, argc, argv); -} - -#include <tst_xdgshellv6.moc> diff --git a/tests/auto/client/xdgshellv6/xdgshellv6.pro b/tests/auto/client/xdgshellv6/xdgshellv6.pro deleted file mode 100644 index 4fec593df..000000000 --- a/tests/auto/client/xdgshellv6/xdgshellv6.pro +++ /dev/null @@ -1,5 +0,0 @@ -include (../shared/shared.pri) - -TARGET = tst_client_xdgshellv6 -SOURCES += tst_xdgshellv6.cpp - diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index 270d11aa6..ec8a54567 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -1,18 +1,19 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 2.8) - -project(qmake_cmake_files) +cmake_minimum_required(VERSION 3.16) +project(qtwayland_cmake_tests) enable_testing() -find_package(Qt5Core REQUIRED) -set(Qt5_MODULE_TEST_DEPENDS Quick) +find_package(Qt6Core REQUIRED) +set(Qt6_MODULE_TEST_DEPENDS Quick) -include("${_Qt5CTestMacros}") +include("${_Qt6CTestMacros}") -test_module_includes( +_qt_internal_test_module_includes( WaylandCompositor QWaylandBufferRef ) -# Can't test in `test_module_includes`, WaylandClient has no public headers -expect_pass(test_waylandclient) +# Can't test in `_qt_internal_test_module_includes`, WaylandClient has no public headers +_qt_internal_test_expect_pass(test_waylandclient) diff --git a/tests/auto/cmake/cmake.pro b/tests/auto/cmake/cmake.pro deleted file mode 100644 index 5098c2ce4..000000000 --- a/tests/auto/cmake/cmake.pro +++ /dev/null @@ -1,6 +0,0 @@ -# Cause make to do nothing. -TEMPLATE = subdirs - -CMAKE_QT_MODULES_UNDER_TEST = waylandclient - -CONFIG += ctest_testcase diff --git a/tests/auto/cmake/test_waylandclient/CMakeLists.txt b/tests/auto/cmake/test_waylandclient/CMakeLists.txt index 3788a4927..cad8d45d3 100644 --- a/tests/auto/cmake/test_waylandclient/CMakeLists.txt +++ b/tests/auto/cmake/test_waylandclient/CMakeLists.txt @@ -1,12 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + project(test_plugins) -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.16) cmake_policy(SET CMP0056 NEW) -find_package(Qt5WaylandClient REQUIRED) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Core_EXECUTABLE_COMPILE_FLAGS}") - -include_directories(${Qt5WaylandClient_PRIVATE_INCLUDE_DIRS}) +find_package(Qt6WaylandClient REQUIRED) add_executable(test_waylandclient_exe main.cpp) -target_link_libraries(test_waylandclient_exe Qt5::WaylandClient) +target_link_libraries(test_waylandclient_exe Qt6::WaylandClientPrivate) diff --git a/tests/auto/compositor/CMakeLists.txt b/tests/auto/compositor/CMakeLists.txt new file mode 100644 index 000000000..3a6dfedb3 --- /dev/null +++ b/tests/auto/compositor/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from compositor.pro. + +add_subdirectory(compositor) diff --git a/tests/auto/compositor/compositor.pro b/tests/auto/compositor/compositor.pro deleted file mode 100644 index 6bf2aef6c..000000000 --- a/tests/auto/compositor/compositor.pro +++ /dev/null @@ -1,3 +0,0 @@ -TEMPLATE=subdirs - -SUBDIRS += compositor diff --git a/tests/auto/compositor/compositor/BLACKLIST b/tests/auto/compositor/compositor/BLACKLIST deleted file mode 100644 index df4672be3..000000000 --- a/tests/auto/compositor/compositor/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[keyboardGrab] -ubuntu-14.04 diff --git a/tests/auto/compositor/compositor/CMakeLists.txt b/tests/auto/compositor/compositor/CMakeLists.txt new file mode 100644 index 000000000..5cb18b2aa --- /dev/null +++ b/tests/auto/compositor/compositor/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from compositor.pro. + +##################################################################### +## tst_compositor Test: +##################################################################### + +qt_internal_add_test(tst_compositor + SOURCES + mockclient.cpp mockclient.h + mockkeyboard.cpp mockkeyboard.h + mockpointer.cpp mockpointer.h + mockseat.cpp mockseat.h + mockxdgoutputv1.cpp mockxdgoutputv1.h + testcompositor.cpp testcompositor.h + testkeyboardgrabber.cpp testkeyboardgrabber.h + testseat.cpp testseat.h + tst_compositor.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + Qt::WaylandCompositor + Qt::WaylandCompositorPrivate + Wayland::Client + Wayland::Server +) + +qt6_generate_wayland_protocol_client_sources(tst_compositor + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/3rdparty/protocol/idle-inhibit-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/3rdparty/protocol/ivi-application.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/3rdparty/protocol/viewporter.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/3rdparty/protocol/wayland.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/3rdparty/protocol/xdg-output-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/3rdparty/protocol/xdg-shell.xml +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(tst_compositor CONDITION QT_FEATURE_xkbcommon + LIBRARIES + XKB::XKB +) diff --git a/tests/auto/compositor/compositor/compositor.pro b/tests/auto/compositor/compositor/compositor.pro deleted file mode 100644 index 0ce2c6be0..000000000 --- a/tests/auto/compositor/compositor/compositor.pro +++ /dev/null @@ -1,34 +0,0 @@ -CONFIG += testcase link_pkgconfig -CONFIG += wayland-scanner -TARGET = tst_compositor - -QT += testlib -QT += core-private gui-private waylandcompositor waylandcompositor-private - -QMAKE_USE += wayland-client wayland-server - -qtConfig(xkbcommon): \ - QMAKE_USE += xkbcommon - -WAYLANDCLIENTSOURCES += \ - ../../../../src/3rdparty/protocol/xdg-shell-unstable-v5.xml \ - ../../../../src/3rdparty/protocol/ivi-application.xml \ - -SOURCES += \ - tst_compositor.cpp \ - testcompositor.cpp \ - testkeyboardgrabber.cpp \ - mockclient.cpp \ - mockseat.cpp \ - testseat.cpp \ - mockkeyboard.cpp \ - mockpointer.cpp - -HEADERS += \ - testcompositor.h \ - testkeyboardgrabber.h \ - mockclient.h \ - mockseat.h \ - testseat.h \ - mockkeyboard.h \ - mockpointer.h diff --git a/tests/auto/compositor/compositor/mockclient.cpp b/tests/auto/compositor/compositor/mockclient.cpp index f74314407..6465f9931 100644 --- a/tests/auto/compositor/compositor/mockclient.cpp +++ b/tests/auto/compositor/compositor/mockclient.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockclient.h" #include "mockseat.h" @@ -57,7 +32,7 @@ MockClient::MockClient() fd = wl_display_get_fd(display); QSocketNotifier *readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); - connect(readNotifier, SIGNAL(activated(int)), this, SLOT(readEvents())); + connect(readNotifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(readEvents())); QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher; connect(dispatcher, SIGNAL(awake()), this, SLOT(flushDisplay())); @@ -76,7 +51,9 @@ const wl_output_listener MockClient::outputListener = { MockClient::outputGeometryEvent, MockClient::outputModeEvent, MockClient::outputDone, - MockClient::outputScale + MockClient::outputScale, + MockClient::outputName, + MockClient::outputDesc }; MockClient::~MockClient() @@ -123,6 +100,16 @@ void MockClient::outputScale(void *, wl_output *, int) } +void MockClient::outputName(void *, wl_output *, const char *) +{ + +} + +void MockClient::outputDesc(void *, wl_output *, const char *) +{ + +} + void MockClient::readEvents() { if (error) @@ -166,27 +153,37 @@ void MockClient::handleGlobalRemove(void *data, wl_registry *wl_registry, uint32 void MockClient::handleGlobal(uint32_t id, const QByteArray &interface) { if (interface == "wl_compositor") { - compositor = static_cast<wl_compositor *>(wl_registry_bind(registry, id, &wl_compositor_interface, 3)); + compositor = static_cast<wl_compositor *>(wl_registry_bind(registry, id, &wl_compositor_interface, 4)); } else if (interface == "wl_output") { auto output = static_cast<wl_output *>(wl_registry_bind(registry, id, &wl_output_interface, 2)); m_outputs.insert(id, output); wl_output_add_listener(output, &outputListener, this); } else if (interface == "wl_shm") { shm = static_cast<wl_shm *>(wl_registry_bind(registry, id, &wl_shm_interface, 1)); + } else if (interface == "wp_viewporter") { + viewporter = static_cast<wp_viewporter *>(wl_registry_bind(registry, id, &wp_viewporter_interface, 1)); } else if (interface == "wl_shell") { wlshell = static_cast<wl_shell *>(wl_registry_bind(registry, id, &wl_shell_interface, 1)); - } else if (interface == "xdg_shell") { - xdgShell = static_cast<xdg_shell *>(wl_registry_bind(registry, id, &xdg_shell_interface, 1)); + } else if (interface == "xdg_wm_base") { + xdgWmBase = static_cast<xdg_wm_base *>(wl_registry_bind(registry, id, &xdg_wm_base_interface, 1)); } else if (interface == "ivi_application") { iviApplication = static_cast<ivi_application *>(wl_registry_bind(registry, id, &ivi_application_interface, 1)); } else if (interface == "wl_seat") { wl_seat *s = static_cast<wl_seat *>(wl_registry_bind(registry, id, &wl_seat_interface, 1)); m_seats << new MockSeat(s); + } else if (interface == "zwp_idle_inhibit_manager_v1") { + idleInhibitManager = static_cast<zwp_idle_inhibit_manager_v1 *>(wl_registry_bind(registry, id, &zwp_idle_inhibit_manager_v1_interface, 1)); + } else if (interface == "zxdg_output_manager_v1") { + xdgOutputManager = new QtWayland::zxdg_output_manager_v1(registry, id, 2); } } void MockClient::handleGlobalRemove(uint32_t id) { + auto *output = m_outputs[id]; + if (m_xdgOutputs.contains(output)) + delete m_xdgOutputs.take(output); + m_outputs.remove(id); } @@ -205,7 +202,13 @@ wl_shell_surface *MockClient::createShellSurface(wl_surface *surface) xdg_surface *MockClient::createXdgSurface(wl_surface *surface) { flushDisplay(); - return xdg_shell_get_xdg_surface(xdgShell, surface); + return xdg_wm_base_get_xdg_surface(xdgWmBase, surface); +} + +xdg_toplevel *MockClient::createXdgToplevel(xdg_surface *xdgSurface) +{ + flushDisplay(); + return xdg_surface_get_toplevel(xdgSurface); } ivi_surface *MockClient::createIviSurface(wl_surface *surface, uint iviId) @@ -214,6 +217,23 @@ ivi_surface *MockClient::createIviSurface(wl_surface *surface, uint iviId) return ivi_application_surface_create(iviApplication, iviId, surface); } +zwp_idle_inhibitor_v1 *MockClient::createIdleInhibitor(wl_surface *surface) +{ + flushDisplay(); + + auto *idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( + idleInhibitManager, surface); + zwp_idle_inhibitor_v1_set_user_data(idleInhibitor, this); + return idleInhibitor; +} + +MockXdgOutputV1 *MockClient::createXdgOutput(wl_output *output) +{ + auto *xdgOutput = new MockXdgOutputV1(xdgOutputManager->get_xdg_output(output)); + m_xdgOutputs[output] = xdgOutput; + return xdgOutput; +} + ShmBuffer::ShmBuffer(const QSize &size, wl_shm *shm) { int stride = size.width() * 4; diff --git a/tests/auto/compositor/compositor/mockclient.h b/tests/auto/compositor/compositor/mockclient.h index 6bfb652ed..2f6d9046e 100644 --- a/tests/auto/compositor/compositor/mockclient.h +++ b/tests/auto/compositor/compositor/mockclient.h @@ -1,41 +1,21 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <wayland-client.h> -#include <qwayland-xdg-shell-unstable-v5.h> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "wayland-wayland-client-protocol.h" +#include <qwayland-xdg-shell.h> #include <wayland-ivi-application-client-protocol.h> +#include "wayland-viewporter-client-protocol.h" +#include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include <QObject> #include <QImage> #include <QRect> #include <QList> +#include <QtCore/QMap> #include <QWaylandOutputMode> +#include "mockxdgoutputv1.h" + class MockSeat; class ShmBuffer @@ -60,16 +40,23 @@ public: wl_surface *createSurface(); wl_shell_surface *createShellSurface(wl_surface *surface); xdg_surface *createXdgSurface(wl_surface *surface); + xdg_toplevel *createXdgToplevel(xdg_surface *xdgSurface); ivi_surface *createIviSurface(wl_surface *surface, uint iviId); + zwp_idle_inhibitor_v1 *createIdleInhibitor(wl_surface *surface); + MockXdgOutputV1 *createXdgOutput(wl_output *output); wl_display *display = nullptr; wl_compositor *compositor = nullptr; QMap<uint, wl_output *> m_outputs; + QMap<wl_output *, MockXdgOutputV1 *> m_xdgOutputs; wl_shm *shm = nullptr; wl_registry *registry = nullptr; wl_shell *wlshell = nullptr; - xdg_shell *xdgShell = nullptr; + xdg_wm_base *xdgWmBase = nullptr; + wp_viewporter *viewporter = nullptr; ivi_application *iviApplication = nullptr; + zwp_idle_inhibit_manager_v1 *idleInhibitManager = nullptr; + QtWayland::zxdg_output_manager_v1 *xdgOutputManager = nullptr; QList<MockSeat *> m_seats; @@ -116,6 +103,8 @@ private: int refreshRate); static void outputDone(void *data, wl_output *output); static void outputScale(void *data, wl_output *output, int factor); + static void outputName(void *data, wl_output *output, const char *name); + static void outputDesc(void *data, wl_output *output, const char *desc); void handleGlobal(uint32_t id, const QByteArray &interface); void handleGlobalRemove(uint32_t id); diff --git a/tests/auto/compositor/compositor/mockkeyboard.cpp b/tests/auto/compositor/compositor/mockkeyboard.cpp index e5f5f8d36..b3055ffa9 100644 --- a/tests/auto/compositor/compositor/mockkeyboard.cpp +++ b/tests/auto/compositor/compositor/mockkeyboard.cpp @@ -1,33 +1,11 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockkeyboard.h" +QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers") +QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers") + void keyboardKeymap(void *keyboard, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { Q_UNUSED(keyboard); diff --git a/tests/auto/compositor/compositor/mockkeyboard.h b/tests/auto/compositor/compositor/mockkeyboard.h index 1090db597..093586bb3 100644 --- a/tests/auto/compositor/compositor/mockkeyboard.h +++ b/tests/auto/compositor/compositor/mockkeyboard.h @@ -1,36 +1,11 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef MOCKKEYBOARD_H #define MOCKKEYBOARD_H #include <QObject> -#include <wayland-client.h> +#include "wayland-wayland-client-protocol.h" class MockKeyboard : public QObject { diff --git a/tests/auto/compositor/compositor/mockpointer.cpp b/tests/auto/compositor/compositor/mockpointer.cpp index 6c51d8bd1..65a8b9ce7 100644 --- a/tests/auto/compositor/compositor/mockpointer.cpp +++ b/tests/auto/compositor/compositor/mockpointer.cpp @@ -1,33 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockpointer.h" +QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers") +QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers") + static void pointerEnter(void *pointer, struct wl_pointer *wlPointer, uint serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { Q_UNUSED(wlPointer); diff --git a/tests/auto/compositor/compositor/mockpointer.h b/tests/auto/compositor/compositor/mockpointer.h index 2054040fd..db5c6a0af 100644 --- a/tests/auto/compositor/compositor/mockpointer.h +++ b/tests/auto/compositor/compositor/mockpointer.h @@ -1,36 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef MOCKPOINTER_H #define MOCKPOINTER_H #include <QObject> -#include <wayland-client.h> +#include "wayland-wayland-client-protocol.h" class MockPointer : public QObject { diff --git a/tests/auto/compositor/compositor/mockseat.cpp b/tests/auto/compositor/compositor/mockseat.cpp index ce873c129..88b1c94e4 100644 --- a/tests/auto/compositor/compositor/mockseat.cpp +++ b/tests/auto/compositor/compositor/mockseat.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 LG Electronics Ltd., author: <mikko.levonmaa@lge.com> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 LG Electronics Ltd., author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockseat.h" diff --git a/tests/auto/compositor/compositor/mockseat.h b/tests/auto/compositor/compositor/mockseat.h index f8c103ed4..12a14c727 100644 --- a/tests/auto/compositor/compositor/mockseat.h +++ b/tests/auto/compositor/compositor/mockseat.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 LG Electronics Ltd., author: <mikko.levonmaa@lge.com> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 LG Electronics Ltd., author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef MOCKSEAT #define MOCKSEAT @@ -32,7 +7,7 @@ #include "mockkeyboard.h" #include <QObject> -#include <wayland-client.h> +#include "wayland-wayland-client-protocol.h" class MockSeat : public QObject { diff --git a/tests/auto/compositor/compositor/mockxdgoutputv1.cpp b/tests/auto/compositor/compositor/mockxdgoutputv1.cpp new file mode 100644 index 000000000..9349e62d6 --- /dev/null +++ b/tests/auto/compositor/compositor/mockxdgoutputv1.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2019 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockxdgoutputv1.h" + +MockXdgOutputV1::MockXdgOutputV1(struct ::zxdg_output_v1 *object) + : QtWayland::zxdg_output_v1(object) +{ +} + +MockXdgOutputV1::~MockXdgOutputV1() +{ + destroy(); +} + +void MockXdgOutputV1::zxdg_output_v1_logical_position(int32_t x, int32_t y) +{ + pending.logicalPosition = QPoint(x, y); +} + +void MockXdgOutputV1::zxdg_output_v1_logical_size(int32_t width, int32_t height) +{ + pending.logicalSize = QSize(width, height); +} + +void MockXdgOutputV1::zxdg_output_v1_done() +{ + // In version 3 we'll have to do this for wl_output.done as well + name = pending.name; + description = pending.description; + logicalPosition = pending.logicalPosition; + logicalSize = pending.logicalSize; +} + +void MockXdgOutputV1::zxdg_output_v1_name(const QString &name) +{ + pending.name = name; +} + +void MockXdgOutputV1::zxdg_output_v1_description(const QString &description) +{ + pending.description = description; +} diff --git a/tests/auto/compositor/compositor/mockxdgoutputv1.h b/tests/auto/compositor/compositor/mockxdgoutputv1.h new file mode 100644 index 000000000..4e3a05c91 --- /dev/null +++ b/tests/auto/compositor/compositor/mockxdgoutputv1.h @@ -0,0 +1,39 @@ +// Copyright (C) 2019 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKXDGOUTPUTV1_H +#define MOCKXDGOUTPUTV1_H + +#include <QPoint> +#include <QSize> +#include <QString> + +#include "qwayland-xdg-output-unstable-v1.h" + +class MockXdgOutputV1 : public QtWayland::zxdg_output_v1 +{ +public: + explicit MockXdgOutputV1(struct ::zxdg_output_v1 *object); + ~MockXdgOutputV1(); + + QString name; + QString description; + QPoint logicalPosition; + QSize logicalSize; + + struct { + QString name; + QString description; + QPoint logicalPosition; + QSize logicalSize; + } pending; + +protected: + void zxdg_output_v1_logical_position(int32_t x, int32_t y) override; + void zxdg_output_v1_logical_size(int32_t width, int32_t height) override; + void zxdg_output_v1_done() override; + void zxdg_output_v1_name(const QString &name) override; + void zxdg_output_v1_description(const QString &description) override; +}; + +#endif // MOCKXDGOUTPUTV1_H diff --git a/tests/auto/compositor/compositor/testcompositor.cpp b/tests/auto/compositor/compositor/testcompositor.cpp index 710bb7b3a..d2f454a93 100644 --- a/tests/auto/compositor/compositor/testcompositor.cpp +++ b/tests/auto/compositor/compositor/testcompositor.cpp @@ -1,36 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "testcompositor.h" #include "testseat.h" #include "testkeyboardgrabber.h" -#include <wayland-server.h> +#include <wayland-server-core.h> TestCompositor::TestCompositor(bool createInputDev) : shell(new QWaylandWlShell(this)) @@ -41,7 +16,9 @@ TestCompositor::TestCompositor(bool createInputDev) void TestCompositor::create() { - new QWaylandOutput(this, nullptr); + auto output = new QWaylandOutput(this, nullptr); + setDefaultOutput(output); + QWaylandCompositor::create(); connect(this, &QWaylandCompositor::surfaceCreated, this, &TestCompositor::onSurfaceCreated); diff --git a/tests/auto/compositor/compositor/testcompositor.h b/tests/auto/compositor/compositor/testcompositor.h index 7829f1a65..0e11def13 100644 --- a/tests/auto/compositor/compositor/testcompositor.h +++ b/tests/auto/compositor/compositor/testcompositor.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwaylandcompositor.h" #include "qwaylandsurface.h" diff --git a/tests/auto/compositor/compositor/testkeyboardgrabber.cpp b/tests/auto/compositor/compositor/testkeyboardgrabber.cpp index a3aa42ac2..73592dd4f 100644 --- a/tests/auto/compositor/compositor/testkeyboardgrabber.cpp +++ b/tests/auto/compositor/compositor/testkeyboardgrabber.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "testkeyboardgrabber.h" diff --git a/tests/auto/compositor/compositor/testkeyboardgrabber.h b/tests/auto/compositor/compositor/testkeyboardgrabber.h index 7f0f2c86c..2e2f44df4 100644 --- a/tests/auto/compositor/compositor/testkeyboardgrabber.h +++ b/tests/auto/compositor/compositor/testkeyboardgrabber.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwaylandkeyboard.h" diff --git a/tests/auto/compositor/compositor/testseat.cpp b/tests/auto/compositor/compositor/testseat.cpp index 38227872b..21e2bffe5 100644 --- a/tests/auto/compositor/compositor/testseat.cpp +++ b/tests/auto/compositor/compositor/testseat.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "testseat.h" #include <QMouseEvent> @@ -49,7 +24,8 @@ bool TestSeat::isOwner(QInputEvent *event) const QList<QMouseEvent *> TestSeat::createMouseEvents(int count) { for (int i = 0; i < count; i++) { - m_events.append(new QMouseEvent(QEvent::MouseMove, QPointF(10 + i, 10 + i), Qt::NoButton, Qt::NoButton, Qt::NoModifier)); + m_events.append(new QMouseEvent(QEvent::MouseMove, QPointF(10 + i, 10 + i), + QPointF(10 + i, 10 + i), Qt::NoButton, Qt::NoButton, Qt::NoModifier)); } return m_events; } diff --git a/tests/auto/compositor/compositor/testseat.h b/tests/auto/compositor/compositor/testseat.h index f4449f144..a71abc122 100644 --- a/tests/auto/compositor/compositor/testseat.h +++ b/tests/auto/compositor/compositor/testseat.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 LG Electronics, Inc., author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QWaylandSeat> #include <QList> diff --git a/tests/auto/compositor/compositor/tst_compositor.cpp b/tests/auto/compositor/compositor/tst_compositor.cpp index e12aa564e..c3c0c35fb 100644 --- a/tests/auto/compositor/compositor/tst_compositor.cpp +++ b/tests/auto/compositor/compositor/tst_compositor.cpp @@ -1,34 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "mockclient.h" #include "mockseat.h" #include "mockpointer.h" +#include "mockxdgoutputv1.h" #include "testcompositor.h" #include "testkeyboardgrabber.h" #include "testseat.h" @@ -38,16 +14,20 @@ #include "qwaylandseat.h" #include <QtGui/QScreen> -#include <QtWaylandCompositor/QWaylandXdgShellV5> -#include <QtWaylandCompositor/private/qwaylandxdgshellv6_p.h> +#include <QtWaylandCompositor/QWaylandXdgShell> #include <QtWaylandCompositor/private/qwaylandkeyboard_p.h> #include <QtWaylandCompositor/QWaylandIviApplication> #include <QtWaylandCompositor/QWaylandIviSurface> #include <QtWaylandCompositor/QWaylandSurface> #include <QtWaylandCompositor/QWaylandResource> #include <QtWaylandCompositor/QWaylandKeymap> -#include <qwayland-xdg-shell-unstable-v5.h> +#include <QtWaylandCompositor/QWaylandViewporter> +#include <QtWaylandCompositor/QWaylandIdleInhibitManagerV1> +#include <QtWaylandCompositor/QWaylandXdgOutputManagerV1> +#include <qwayland-xdg-shell.h> #include <qwayland-ivi-application.h> +#include <QtWaylandCompositor/private/qwaylandoutput_p.h> +#include <QtWaylandCompositor/private/qwaylandsurface_p.h> #include <QtTest/QtTest> @@ -68,16 +48,19 @@ private slots: void seatKeyboardFocus(); void seatMouseFocus(); void inputRegion(); + void defaultInputRegionHiDpi(); void singleClient(); void multipleClients(); void geometry(); + void availableGeometry(); void modes(); void comparingModes(); void sizeFollowsWindow(); void mapSurface(); void mapSurfaceHiDpi(); void frameCallback(); - void removeOutput(); + void pixelFormats(); + void outputs(); void customSurface(); void advertisesXdgShellSupport(); @@ -92,12 +75,32 @@ private slots: void sendsIviConfigure(); void destroysIviSurfaces(); - void convertsXdgEdgesToQtEdges(); - void xdgShellV6Positioner(); + void viewporterGlobal(); + void viewportDestination(); + void viewportSource(); + void viewportSourceAndDestination(); + void viewportDestruction(); + void viewportProtocolErrors_data(); + void viewportProtocolErrors(); + void viewportClearDestination(); + void viewportClearSource(); + void viewportExistsError(); + void viewportDestinationNoSurfaceError(); + void viewportSourceNoSurfaceError(); + void viewportHiDpi(); + + void idleInhibit(); + + void xdgOutput(); + +private: + QTemporaryDir m_tmpRuntimeDir; }; void tst_WaylandCompositor::init() { - qputenv("XDG_RUNTIME_DIR", "."); + // We need to set a test specific runtime dir so we don't conflict with other tests' + // compositors by accident. + qputenv("XDG_RUNTIME_DIR", m_tmpRuntimeDir.path().toLocal8Bit()); } void tst_WaylandCompositor::singleClient() @@ -309,14 +312,14 @@ void tst_WaylandCompositor::keyboardGrab() //QSignalSpy grabModifierSpy(grab, SIGNAL(modifiersCalled())); seat->setKeyboardFocus(waylandSurface); - QTRY_COMPARE(grabFocusSpy.count(), 1); + QTRY_COMPARE(grabFocusSpy.size(), 1); QKeyEvent ke(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, 30, 0, 0); QKeyEvent ke1(QEvent::KeyRelease, Qt::Key_A, Qt::NoModifier, 30, 0, 0); seat->sendFullKeyEvent(&ke); seat->sendFullKeyEvent(&ke1); - QTRY_COMPARE(grabKeyPressSpy.count(), 1); - QTRY_COMPARE(grabKeyReleaseSpy.count(), 1); + QTRY_COMPARE(grabKeyPressSpy.size(), 1); + QTRY_COMPARE(grabKeyReleaseSpy.size(), 1); QKeyEvent ke2(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier, 50, 0, 0); QKeyEvent ke3(QEvent::KeyRelease, Qt::Key_Shift, Qt::NoModifier, 50, 0, 0); @@ -324,14 +327,14 @@ void tst_WaylandCompositor::keyboardGrab() seat->sendFullKeyEvent(&ke3); //QTRY_COMPARE(grabModifierSpy.count(), 2); // Modifiers are also keys - QTRY_COMPARE(grabKeyPressSpy.count(), 2); - QTRY_COMPARE(grabKeyReleaseSpy.count(), 2); + QTRY_COMPARE(grabKeyPressSpy.size(), 2); + QTRY_COMPARE(grabKeyReleaseSpy.size(), 2); // Stop grabbing seat->setKeyboardFocus(nullptr); seat->sendFullKeyEvent(&ke); seat->sendFullKeyEvent(&ke1); - QTRY_COMPARE(grabKeyPressSpy.count(), 2); + QTRY_COMPARE(grabKeyPressSpy.size(), 2); } void tst_WaylandCompositor::geometry() @@ -351,6 +354,22 @@ void tst_WaylandCompositor::geometry() QTRY_COMPARE(client.refreshRate, 60000); } +void tst_WaylandCompositor::availableGeometry() +{ + TestCompositor compositor; + compositor.create(); + + QWaylandOutputMode mode(QSize(1024, 768), 60000); + compositor.defaultOutput()->addMode(mode, true); + compositor.defaultOutput()->setCurrentMode(mode); + + MockClient client; + + QRect availableGeometry(50, 100, 850, 600); + compositor.defaultOutput()->setAvailableGeometry(availableGeometry); + QCOMPARE(compositor.defaultOutput()->availableGeometry(), availableGeometry); +} + void tst_WaylandCompositor::modes() { TestCompositor compositor; @@ -434,7 +453,8 @@ void tst_WaylandCompositor::mapSurface() QSignalSpy hasContentSpy(waylandSurface, SIGNAL(hasContentChanged())); - QCOMPARE(waylandSurface->size(), QSize()); + QCOMPARE(waylandSurface->bufferSize(), QSize()); + QCOMPARE(waylandSurface->destinationSize(), QSize()); QCOMPARE(waylandSurface->hasContent(), false); QSize size(256, 256); @@ -446,9 +466,10 @@ void tst_WaylandCompositor::mapSurface() wl_surface_damage(surface, 0, 0, size.width(), size.height()); wl_surface_commit(surface); - QTRY_COMPARE(hasContentSpy.count(), 1); + QTRY_COMPARE(hasContentSpy.size(), 1); QCOMPARE(waylandSurface->hasContent(), true); - QCOMPARE(waylandSurface->size(), size); + QCOMPARE(waylandSurface->bufferSize(), size); + QCOMPARE(waylandSurface->destinationSize(), size); wl_surface_destroy(surface); } @@ -478,46 +499,61 @@ void tst_WaylandCompositor::mapSurfaceHiDpi() wl_surface_damage(surface, 0, 0, surfaceSize.width(), surfaceSize.height()); auto verifyComittedState = [=]() { - QCOMPARE(waylandSurface->size(), bufferSize); + QCOMPARE(waylandSurface->bufferSize(), bufferSize); + QCOMPARE(waylandSurface->destinationSize(), surfaceSize); QCOMPARE(waylandSurface->bufferScale(), bufferScale); QCOMPARE(waylandSurface->hasContent(), true); }; - QObject::connect(waylandSurface, &QWaylandSurface::damaged, [=] (const QRegion &damage) { - // Currently, QWaylandSurface::size returns the size in pixels. - // Should be fixed or removed for Qt 6. + QObject::connect(waylandSurface, &QWaylandSurface::damaged, this, [=] (const QRegion &damage) { QCOMPARE(damage, QRect(QPoint(), surfaceSize)); verifyComittedState(); }); QSignalSpy damagedSpy(waylandSurface, SIGNAL(damaged(const QRegion &))); - QObject::connect(waylandSurface, &QWaylandSurface::hasContentChanged, verifyComittedState); + QObject::connect(waylandSurface, &QWaylandSurface::hasContentChanged, + this, verifyComittedState); QSignalSpy hasContentSpy(waylandSurface, SIGNAL(hasContentChanged())); - QObject::connect(waylandSurface, &QWaylandSurface::sizeChanged, verifyComittedState); - QSignalSpy sizeSpy(waylandSurface, SIGNAL(sizeChanged())); + QObject::connect(waylandSurface, &QWaylandSurface::bufferSizeChanged, + this, verifyComittedState); + QSignalSpy bufferSizeSpy(waylandSurface, SIGNAL(bufferSizeChanged())); - QObject::connect(waylandSurface, &QWaylandSurface::bufferScaleChanged, verifyComittedState); + QObject::connect(waylandSurface, &QWaylandSurface::destinationSizeChanged, + this, verifyComittedState); + QSignalSpy destinationSizeSpy(waylandSurface, SIGNAL(destinationSizeChanged())); + + QObject::connect(waylandSurface, &QWaylandSurface::bufferScaleChanged, + this, verifyComittedState); QSignalSpy bufferScaleSpy(waylandSurface, SIGNAL(bufferScaleChanged())); - QObject::connect(waylandSurface, &QWaylandSurface::offsetForNextFrame, [=](const QPoint &offset) { + QObject::connect(waylandSurface, &QWaylandSurface::offsetForNextFrame, + this, [=](const QPoint &offset) { QCOMPARE(offset, attachOffset); verifyComittedState(); }); QSignalSpy offsetSpy(waylandSurface, SIGNAL(offsetForNextFrame(const QPoint &))); // No state should be applied before the commit - QCOMPARE(waylandSurface->size(), QSize()); + QCOMPARE(waylandSurface->bufferSize(), QSize()); + QCOMPARE(waylandSurface->destinationSize(), QSize()); QCOMPARE(waylandSurface->hasContent(), false); QCOMPARE(waylandSurface->bufferScale(), 1); - QCOMPARE(offsetSpy.count(), 0); + QCOMPARE(offsetSpy.size(), 0); wl_surface_commit(surface); - QTRY_COMPARE(hasContentSpy.count(), 1); - QTRY_COMPARE(sizeSpy.count(), 1); - QTRY_COMPARE(bufferScaleSpy.count(), 1); - QTRY_COMPARE(offsetSpy.count(), 1); + QTRY_COMPARE(hasContentSpy.size(), 1); + QTRY_COMPARE(bufferSizeSpy.size(), 1); + QTRY_COMPARE(destinationSizeSpy.size(), 1); + QTRY_COMPARE(bufferScaleSpy.size(), 1); + QTRY_COMPARE(offsetSpy.size(), 1); + QTRY_COMPARE(damagedSpy.size(), 1); + + // Now verify that wl_surface_damage_buffer gets mapped properly + wl_surface_damage_buffer(surface, 0, 0, bufferSize.width(), bufferSize.height()); + wl_surface_commit(surface); + QTRY_COMPARE(damagedSpy.size(), 2); wl_surface_destroy(surface); } @@ -537,27 +573,27 @@ static void registerFrameCallback(wl_surface *surface, int *counter) wl_callback_add_listener(wl_surface_frame(surface), &frameCallbackListener, counter); } -void tst_WaylandCompositor::frameCallback() +class BufferView : public QWaylandView { - class BufferView : public QWaylandView +public: + void bufferCommitted(const QWaylandBufferRef &ref, const QRegion &damage) override { - public: - void bufferCommitted(const QWaylandBufferRef &ref, const QRegion &damage) override - { - Q_UNUSED(damage); - bufferRef = ref; - } + Q_UNUSED(damage); + bufferRef = ref; + } - QImage image() const - { - if (bufferRef.isNull() || !bufferRef.isSharedMemory()) - return QImage(); - return bufferRef.image(); - } + QImage image() const + { + if (bufferRef.isNull() || !bufferRef.isSharedMemory()) + return QImage(); + return bufferRef.image(); + } - QWaylandBufferRef bufferRef; - }; + QWaylandBufferRef bufferRef; +}; +void tst_WaylandCompositor::frameCallback() +{ TestCompositor compositor; compositor.create(); @@ -586,7 +622,7 @@ void tst_WaylandCompositor::frameCallback() wl_surface_commit(surface); QTRY_COMPARE(waylandSurface->hasContent(), true); - QTRY_COMPARE(damagedSpy.count(), i + 1); + QTRY_COMPARE(damagedSpy.size(), i + 1); QCOMPARE(static_cast<BufferView*>(waylandSurface->views().first())->image(), buffer.image); compositor.defaultOutput()->frameStarted(); @@ -598,18 +634,62 @@ void tst_WaylandCompositor::frameCallback() wl_surface_destroy(surface); } -void tst_WaylandCompositor::removeOutput() +void tst_WaylandCompositor::pixelFormats() { TestCompositor compositor; + compositor.create(); + + MockClient client; + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + BufferView* view = new BufferView; + view->setSurface(waylandSurface); + view->setOutput(compositor.defaultOutput()); + + QSize size(32, 32); + ShmBuffer buffer(size, client.shm); // Will be WL_SHM_FORMAT_ARGB8888; + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, size.width(), size.height()); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->hasContent(), true); + + // According to https://lists.freedesktop.org/archives/wayland-devel/2017-August/034791.html + // all RGB formats with alpha are premultiplied. Verify it here: + QCOMPARE(view->image().format(), QImage::Format_ARGB32_Premultiplied); + + wl_surface_destroy(surface); +} + +void tst_WaylandCompositor::outputs() +{ + TestCompositor compositor; + + QSignalSpy defaultOutputSpy(&compositor, SIGNAL(defaultOutputChanged())); + + compositor.create(); + + QSignalSpy outputAddedSpy(&compositor, SIGNAL(outputAdded(QWaylandOutput*))); + QSignalSpy outputRemovedSpy(&compositor, SIGNAL(outputRemoved(QWaylandOutput*))); + QWindow window; window.resize(800, 600); + auto output = new QWaylandOutput(&compositor, &window); + QTRY_COMPARE(outputAddedSpy.size(), 1); + + compositor.setDefaultOutput(output); + QTRY_COMPARE(defaultOutputSpy.size(), 2); - compositor.create(); MockClient client; QTRY_COMPARE(client.m_outputs.size(), 2); delete output; + QTRY_COMPARE(outputRemovedSpy.size(), 1); + QEXPECT_FAIL("", "FIXME: defaultOutputChanged() is not emitted when the default output is removed", Continue); + QTRY_COMPARE(defaultOutputSpy.size(), 3); compositor.flushClients(); QTRY_COMPARE(client.m_outputs.size(), 1); } @@ -638,6 +718,8 @@ void tst_WaylandCompositor::customSurface() MockClient client; wl_surface *surface = client.createSurface(); QTRY_COMPARE(compositor.surfaces.size(), 1); + wl_surface_destroy(surface); + QTRY_COMPARE(compositor.surfaces.size(), 0); } void tst_WaylandCompositor::seatCapabilities() @@ -674,9 +756,8 @@ void tst_WaylandCompositor::seatCreation() // The compositor will create the default input device QTRY_VERIFY(seat->isInitialized()); - QList<QMouseEvent *> allEvents; - allEvents += seat->createMouseEvents(5); - foreach (QMouseEvent *me, allEvents) { + const QList<QMouseEvent *> allEvents = seat->createMouseEvents(5); + for (QMouseEvent *me : allEvents) { compositor.seatFor(me); } @@ -805,6 +886,11 @@ void tst_WaylandCompositor::inputRegion() QVERIFY(!waylandSurface->inputRegionContains(QPoint(1, 6))); QVERIFY(!waylandSurface->inputRegionContains(QPoint(4, 2))); + QVERIFY(!waylandSurface->inputRegionContains(QPointF(0.99, 1.99))); + QVERIFY(waylandSurface->inputRegionContains(QPointF(1, 2))); + QVERIFY(waylandSurface->inputRegionContains(QPointF(3.99, 4.99))); + QVERIFY(!waylandSurface->inputRegionContains(QPointF(4, 5))); + // Setting a nullptr input region means we want all events wl_surface_set_input_region(surface, nullptr); wl_surface_commit(surface); @@ -826,11 +912,38 @@ void tst_WaylandCompositor::inputRegion() QVERIFY(!waylandSurface->inputRegionContains(QPoint(1, 2))); } +void tst_WaylandCompositor::defaultInputRegionHiDpi() +{ + TestCompositor compositor(true); + compositor.create(); + + MockClient client; + wl_surface *surface = client.createSurface(); + + int bufferScale = 2; + QSize surfaceSize(16, 16); + QSize bufferSize = surfaceSize * bufferScale; + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, surfaceSize.width(), surfaceSize.height()); + wl_surface_set_buffer_scale(surface, bufferScale); + wl_surface_commit(surface); + + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + QCOMPARE(waylandSurface->bufferScale(), bufferScale); + QVERIFY(waylandSurface->inputRegionContains(QPoint(0, 0))); + QVERIFY(waylandSurface->inputRegionContains(QPoint(15, 15))); + QVERIFY(!waylandSurface->inputRegionContains(QPoint(-1, -1))); + QVERIFY(!waylandSurface->inputRegionContains(QPoint(16, 16))); +} + class XdgTestCompositor: public TestCompositor { Q_OBJECT public: XdgTestCompositor() : xdgShell(this) {} - QWaylandXdgShellV5 xdgShell; + QWaylandXdgShell xdgShell; }; void tst_WaylandCompositor::advertisesXdgShellSupport() @@ -839,7 +952,7 @@ void tst_WaylandCompositor::advertisesXdgShellSupport() compositor.create(); MockClient client; - QTRY_VERIFY(client.xdgShell); + QTRY_VERIFY(client.xdgWmBase); } void tst_WaylandCompositor::createsXdgSurfaces() @@ -848,19 +961,21 @@ void tst_WaylandCompositor::createsXdgSurfaces() compositor.create(); MockClient client; - QTRY_VERIFY(client.xdgShell); + QTRY_VERIFY(client.xdgWmBase); - QSignalSpy xdgSurfaceCreatedSpy(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated); - QWaylandXdgSurfaceV5 *xdgSurface = nullptr; - QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *s) { - xdgSurface = s; - }); + QSignalSpy xdgSurfaceCreatedSpy(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated); + QWaylandXdgSurface *xdgSurface = nullptr; + QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, + this, [&](QWaylandXdgSurface *s) { xdgSurface = s; }); wl_surface *surface = client.createSurface(); - client.createXdgSurface(surface); - QTRY_COMPARE(xdgSurfaceCreatedSpy.count(), 1); + xdg_surface *clientXdgSurface = client.createXdgSurface(surface); + QTRY_COMPARE(xdgSurfaceCreatedSpy.size(), 1); QTRY_VERIFY(xdgSurface); QTRY_VERIFY(xdgSurface->surface()); + + xdg_surface_destroy(clientXdgSurface); + wl_surface_destroy(surface); } void tst_WaylandCompositor::reportsXdgSurfaceWindowGeometry() @@ -868,27 +983,35 @@ void tst_WaylandCompositor::reportsXdgSurfaceWindowGeometry() XdgTestCompositor compositor; compositor.create(); - QWaylandXdgSurfaceV5 *xdgSurface = nullptr; - QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *s) { - xdgSurface = s; - }); + QWaylandXdgSurface *xdgSurface = nullptr; + QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, + this, [&](QWaylandXdgSurface *s) { xdgSurface = s; }); MockClient client; wl_surface *surface = client.createSurface(); xdg_surface *clientXdgSurface = client.createXdgSurface(surface); + xdg_toplevel *clientToplevel = client.createXdgToplevel(clientXdgSurface); + QSize size(256, 256); ShmBuffer buffer(size, client.shm); + + QTRY_VERIFY(xdgSurface); + //TODO: Here we should ideally be acking the configure, we're techically making a protocol error + wl_surface_attach(surface, buffer.handle, 0, 0); wl_surface_damage(surface, 0, 0, size.width(), size.height()); wl_surface_commit(surface); - QTRY_VERIFY(xdgSurface); QTRY_COMPARE(xdgSurface->windowGeometry(), QRect(QPoint(0, 0), QSize(256, 256))); xdg_surface_set_window_geometry(clientXdgSurface, 10, 20, 100, 50); wl_surface_commit(surface); QTRY_COMPARE(xdgSurface->windowGeometry(), QRect(QPoint(10, 20), QSize(100, 50))); + + xdg_toplevel_destroy(clientToplevel); + xdg_surface_destroy(clientXdgSurface); + wl_surface_destroy(surface); } void tst_WaylandCompositor::setsXdgAppId() @@ -896,19 +1019,19 @@ void tst_WaylandCompositor::setsXdgAppId() XdgTestCompositor compositor; compositor.create(); - QWaylandXdgSurfaceV5 *xdgSurface = nullptr; - QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *s) { - xdgSurface = s; - }); + QWaylandXdgToplevel *toplevel = nullptr; + QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::toplevelCreated, + this, [&](QWaylandXdgToplevel *t) { toplevel = t; }); MockClient client; wl_surface *surface = client.createSurface(); xdg_surface *clientXdgSurface = client.createXdgSurface(surface); + xdg_toplevel *clientToplevel = client.createXdgToplevel(clientXdgSurface); - xdg_surface_set_app_id(clientXdgSurface, "org.foo.bar"); + xdg_toplevel_set_app_id(clientToplevel, "org.foo.bar"); - QTRY_VERIFY(xdgSurface); - QTRY_COMPARE(xdgSurface->appId(), QString("org.foo.bar")); + QTRY_VERIFY(toplevel); + QTRY_COMPARE(toplevel->appId(), QString("org.foo.bar")); } void tst_WaylandCompositor::sendsXdgConfigure() @@ -916,111 +1039,117 @@ void tst_WaylandCompositor::sendsXdgConfigure() class MockXdgSurface : public QtWayland::xdg_surface { public: - MockXdgSurface(::xdg_surface *xdgSurface) : QtWayland::xdg_surface(xdgSurface) {} - void xdg_surface_configure(int32_t width, int32_t height, wl_array *rawStates, uint32_t serial) override + explicit MockXdgSurface(::xdg_surface *xdgSurface) : QtWayland::xdg_surface(xdgSurface) {} + void xdg_surface_configure(uint32_t serial) override { configureSerial = serial; } + uint configureSerial = 0; + }; + + class MockXdgToplevel : public QtWayland::xdg_toplevel + { + public: + explicit MockXdgToplevel(::xdg_toplevel *toplevel) : QtWayland::xdg_toplevel(toplevel) {} + void xdg_toplevel_configure(int32_t width, int32_t height, wl_array *rawStates) override { configureSize = QSize(width, height); - configureSerial = serial; - uint *states = reinterpret_cast<uint*>(rawStates->data); configureStates.clear(); size_t numStates = rawStates->size / sizeof(uint); - for (size_t i = 0; i < numStates; ++i) { + for (size_t i = 0; i < numStates; ++i) configureStates.push_back(states[i]); - } } - QList<uint> configureStates; QSize configureSize; - uint configureSerial; }; XdgTestCompositor compositor; compositor.create(); - QWaylandXdgSurfaceV5 *xdgSurface = nullptr; - QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *s) { - xdgSurface = s; - }); + QWaylandXdgToplevel *toplevel = nullptr; + QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::toplevelCreated, + this, [&](QWaylandXdgToplevel *t) { toplevel = t; }); MockClient client; wl_surface *surface = client.createSurface(); + xdg_surface *clientXdgSurface = client.createXdgSurface(surface); MockXdgSurface mockXdgSurface(clientXdgSurface); - QTRY_VERIFY(xdgSurface); - QTRY_VERIFY(!xdgSurface->activated()); - QTRY_VERIFY(!xdgSurface->maximized()); - QTRY_VERIFY(!xdgSurface->fullscreen()); - QTRY_VERIFY(!xdgSurface->resizing()); + xdg_toplevel *clientToplevel = client.createXdgToplevel(clientXdgSurface); + MockXdgToplevel mockToplevel(clientToplevel); - xdgSurface->sendConfigure(QSize(10, 20), QVector<QWaylandXdgSurfaceV5::State>{QWaylandXdgSurfaceV5::State::ActivatedState}); + QTRY_VERIFY(toplevel); + QTRY_VERIFY(!toplevel->activated()); + QTRY_VERIFY(!toplevel->maximized()); + QTRY_VERIFY(!toplevel->fullscreen()); + QTRY_VERIFY(!toplevel->resizing()); + + toplevel->sendConfigure(QSize(10, 20), QList<QWaylandXdgToplevel::State>{QWaylandXdgToplevel::State::ActivatedState}); compositor.flushClients(); - QTRY_COMPARE(mockXdgSurface.configureStates, QList<uint>{QWaylandXdgSurfaceV5::State::ActivatedState}); - QTRY_COMPARE(mockXdgSurface.configureSize, QSize(10, 20)); + QTRY_COMPARE(mockToplevel.configureStates, QList<uint>{QWaylandXdgToplevel::State::ActivatedState}); + QTRY_COMPARE(mockToplevel.configureSize, QSize(10, 20)); - xdgSurface->sendMaximized(QSize(800, 600)); + toplevel->sendMaximized(QSize(800, 600)); compositor.flushClients(); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::MaximizedState)); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); - QTRY_COMPARE(mockXdgSurface.configureSize, QSize(800, 600)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::MaximizedState)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ActivatedState)); + QTRY_COMPARE(mockToplevel.configureSize, QSize(800, 600)); // There hasn't been any ack_configures, so state should still be unchanged - QTRY_VERIFY(!xdgSurface->activated()); - QTRY_VERIFY(!xdgSurface->maximized()); + QTRY_VERIFY(!toplevel->activated()); + QTRY_VERIFY(!toplevel->maximized()); xdg_surface_ack_configure(clientXdgSurface, mockXdgSurface.configureSerial); wl_display_dispatch_pending(client.display); wl_display_flush(client.display); - QTRY_VERIFY(xdgSurface->activated()); - QTRY_VERIFY(xdgSurface->maximized()); + QTRY_VERIFY(toplevel->activated()); + QTRY_VERIFY(toplevel->maximized()); - xdgSurface->sendUnmaximized(); + toplevel->sendUnmaximized(); compositor.flushClients(); - QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::MaximizedState)); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); - QTRY_COMPARE(mockXdgSurface.configureSize, QSize(0, 0)); + QTRY_VERIFY(!mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::MaximizedState)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ActivatedState)); + QTRY_COMPARE(mockToplevel.configureSize, QSize(0, 0)); // The unmaximized configure hasn't been acked, so maximized should still be true - QTRY_VERIFY(xdgSurface->maximized()); - QTRY_VERIFY(xdgSurface->activated()); + QTRY_VERIFY(toplevel->maximized()); + QTRY_VERIFY(toplevel->activated()); - xdgSurface->sendResizing(QSize(800, 600)); + toplevel->sendResizing(QSize(800, 600)); compositor.flushClients(); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ResizingState)); - QTRY_COMPARE(mockXdgSurface.configureSize, QSize(800, 600)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ResizingState)); + QTRY_COMPARE(mockToplevel.configureSize, QSize(800, 600)); - xdgSurface->sendFullscreen(QSize(1024, 768)); + toplevel->sendFullscreen(QSize(1024, 768)); compositor.flushClients(); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::FullscreenState)); - QTRY_COMPARE(mockXdgSurface.configureSize, QSize(1024, 768)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ActivatedState)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::FullscreenState)); + QTRY_COMPARE(mockToplevel.configureSize, QSize(1024, 768)); uint fullscreenSerial = mockXdgSurface.configureSerial; - xdgSurface->sendUnmaximized(); + toplevel->sendUnmaximized(); compositor.flushClients(); - QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); - QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::FullscreenState)); + QTRY_VERIFY(mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ActivatedState)); + QTRY_VERIFY(!mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::FullscreenState)); - xdgSurface->sendConfigure(QSize(0, 0), QVector<QWaylandXdgSurfaceV5::State>{}); + toplevel->sendConfigure(QSize(0, 0), QList<QWaylandXdgToplevel::State>{}); compositor.flushClients(); - QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); + QTRY_VERIFY(!mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ActivatedState)); - xdgSurface->sendMaximized(QSize(800, 600)); + toplevel->sendMaximized(QSize(800, 600)); compositor.flushClients(); - QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); + QTRY_VERIFY(!mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::ActivatedState)); - xdgSurface->sendFullscreen(QSize(800, 600)); + toplevel->sendFullscreen(QSize(800, 600)); compositor.flushClients(); - QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::MaximizedState)); + QTRY_VERIFY(!mockToplevel.configureStates.contains(QWaylandXdgToplevel::State::MaximizedState)); // Verify that acking a configure that's not the most recently sent works xdg_surface_ack_configure(clientXdgSurface, fullscreenSerial); wl_display_dispatch_pending(client.display); wl_display_flush(client.display); - QTRY_VERIFY(xdgSurface->fullscreen()); - QTRY_VERIFY(xdgSurface->activated()); - QTRY_VERIFY(!xdgSurface->maximized()); - QTRY_VERIFY(!xdgSurface->resizing()); + QTRY_VERIFY(toplevel->fullscreen()); + QTRY_VERIFY(toplevel->activated()); + QTRY_VERIFY(!toplevel->maximized()); + QTRY_VERIFY(!toplevel->resizing()); } class IviTestCompositor: public TestCompositor { @@ -1049,13 +1178,12 @@ void tst_WaylandCompositor::createsIviSurfaces() QSignalSpy iviSurfaceCreatedSpy(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceRequested); QWaylandIviSurface *iviSurface = nullptr; - QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, [&](QWaylandIviSurface *s) { - iviSurface = s; - }); + QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, + this, [&](QWaylandIviSurface *s) { iviSurface = s; }); wl_surface *surface = client.createSurface(); client.createIviSurface(surface, 123); - QTRY_COMPARE(iviSurfaceCreatedSpy.count(), 1); + QTRY_COMPARE(iviSurfaceCreatedSpy.size(), 1); QTRY_VERIFY(iviSurface); QTRY_VERIFY(iviSurface->surface()); QTRY_COMPARE(iviSurface->iviId(), 123u); @@ -1071,9 +1199,10 @@ void tst_WaylandCompositor::emitsErrorOnSameIviId() QTRY_VERIFY(&firstClient.iviApplication); QWaylandIviSurface *firstIviSurface = nullptr; - QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, [&](QWaylandIviSurface *s) { - firstIviSurface = s; - }); + auto connection = QObject::connect(&compositor.iviApplication, + &QWaylandIviApplication::iviSurfaceCreated, + this, + [&](QWaylandIviSurface *s) { firstIviSurface = s; }); firstClient.createIviSurface(firstClient.createSurface(), 123); QTRY_VERIFY(firstIviSurface); @@ -1082,7 +1211,7 @@ void tst_WaylandCompositor::emitsErrorOnSameIviId() { MockClient secondClient; QTRY_VERIFY(&secondClient.iviApplication); - QTRY_COMPARE(compositor.clients().count(), 2); + QTRY_COMPARE(compositor.clients().size(), 2); secondClient.createIviSurface(secondClient.createSurface(), 123); compositor.flushClients(); @@ -1090,8 +1219,9 @@ void tst_WaylandCompositor::emitsErrorOnSameIviId() QTRY_COMPARE(secondClient.error, EPROTO); QTRY_COMPARE(secondClient.protocolError.interface, &ivi_application_interface); QTRY_COMPARE(static_cast<ivi_application_error>(secondClient.protocolError.code), IVI_APPLICATION_ERROR_IVI_ID); - QTRY_COMPARE(compositor.clients().count(), 1); + QTRY_COMPARE(compositor.clients().size(), 1); } + QObject::disconnect(connection); } // The other clients have passed out of scope and have been destroyed, @@ -1100,9 +1230,8 @@ void tst_WaylandCompositor::emitsErrorOnSameIviId() QTRY_VERIFY(&thirdClient.iviApplication); QWaylandIviSurface *thirdIviSurface = nullptr; - QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, [&](QWaylandIviSurface *s) { - thirdIviSurface = s; - }); + QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, + this, [&](QWaylandIviSurface *s) { thirdIviSurface = s; }); thirdClient.createIviSurface(thirdClient.createSurface(), 123); compositor.flushClients(); @@ -1131,9 +1260,8 @@ void tst_WaylandCompositor::sendsIviConfigure() QTRY_VERIFY(client.iviApplication); QWaylandIviSurface *iviSurface = nullptr; - QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, [&](QWaylandIviSurface *s) { - iviSurface = s; - }); + QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, + this, [&](QWaylandIviSurface *s) { iviSurface = s; }); wl_surface *surface = client.createSurface(); ivi_surface *clientIviSurface = client.createIviSurface(surface, 123); @@ -1155,60 +1283,514 @@ void tst_WaylandCompositor::destroysIviSurfaces() QTRY_VERIFY(client.iviApplication); QWaylandIviSurface *iviSurface = nullptr; - QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, [&](QWaylandIviSurface *s) { - iviSurface = s; - }); + QObject::connect(&compositor.iviApplication, &QWaylandIviApplication::iviSurfaceCreated, + this, [&](QWaylandIviSurface *s) { iviSurface = s; }); QtWayland::ivi_surface mockIviSurface(client.createIviSurface(client.createSurface(), 123)); QTRY_VERIFY(iviSurface); QSignalSpy destroySpy(iviSurface, SIGNAL(destroyed())); mockIviSurface.destroy(); - QTRY_VERIFY(destroySpy.count() == 1); + QTRY_VERIFY(destroySpy.size() == 1); +} + +class ViewporterTestCompositor: public TestCompositor { + Q_OBJECT +public: + ViewporterTestCompositor() : viewporter(this) {} + QWaylandViewporter viewporter; +}; + +void tst_WaylandCompositor::viewporterGlobal() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); +} + +void tst_WaylandCompositor::viewportDestination() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + QCOMPARE(waylandSurface->destinationSize(), QSize()); + QCOMPARE(waylandSurface->sourceGeometry(), QRect()); + + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + const QSize destinationSize(128, 123); + wp_viewport_set_destination(viewport, destinationSize.width(), destinationSize.height()); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->bufferSize(), bufferSize); + QCOMPARE(waylandSurface->destinationSize(), QSize(128, 123)); + QCOMPARE(waylandSurface->sourceGeometry(), QRect(QPoint(), bufferSize)); + + wp_viewport_destroy(viewport); + wl_surface_destroy(surface); + QCOMPARE(client.error, 0); } -void tst_WaylandCompositor::convertsXdgEdgesToQtEdges() +void tst_WaylandCompositor::viewportSource() { - const uint wlLeft = ZXDG_POSITIONER_V6_ANCHOR_LEFT; - QCOMPARE(QWaylandXdgShellV6Private::convertToEdges(wlLeft), Qt::LeftEdge); + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); - const uint wlRight = ZXDG_POSITIONER_V6_ANCHOR_RIGHT; - QCOMPARE(QWaylandXdgShellV6Private::convertToEdges(wlRight), Qt::RightEdge); + QCOMPARE(waylandSurface->destinationSize(), QSize()); + QCOMPARE(waylandSurface->sourceGeometry(), QRect()); - const uint wlTop = ZXDG_POSITIONER_V6_ANCHOR_TOP; - QCOMPARE(QWaylandXdgShellV6Private::convertToEdges(wlTop), Qt::TopEdge); + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + const QRectF sourceGeometry(QPointF(10.5, 20.5), QSizeF(30, 40)); + wp_viewport_set_source(viewport, + wl_fixed_from_double(sourceGeometry.x()), + wl_fixed_from_double(sourceGeometry.y()), + wl_fixed_from_double(sourceGeometry.width()), + wl_fixed_from_double(sourceGeometry.height())); + wl_surface_commit(surface); - const uint wlBottom = ZXDG_POSITIONER_V6_ANCHOR_BOTTOM; - QCOMPARE(QWaylandXdgShellV6Private::convertToEdges(wlBottom), Qt::BottomEdge); + QTRY_COMPARE(waylandSurface->bufferSize(), bufferSize); + QCOMPARE(waylandSurface->destinationSize(), sourceGeometry.size().toSize()); + QCOMPARE(waylandSurface->sourceGeometry(), sourceGeometry); - QCOMPARE(QWaylandXdgShellV6Private::convertToEdges(wlBottom | wlLeft), Qt::Edges(Qt::BottomEdge | Qt::LeftEdge)); - QCOMPARE(QWaylandXdgShellV6Private::convertToEdges(wlTop | wlRight), Qt::Edges(Qt::TopEdge | Qt::RightEdge)); + wp_viewport_destroy(viewport); + wl_surface_destroy(surface); + QCOMPARE(client.error, 0); } -void tst_WaylandCompositor::xdgShellV6Positioner() +void tst_WaylandCompositor::viewportSourceAndDestination() { - QWaylandXdgPositionerV6Data p; - QVERIFY(!p.isComplete()); + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + QCOMPARE(waylandSurface->destinationSize(), QSize()); + QCOMPARE(waylandSurface->sourceGeometry(), QRect()); + + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); - p.size = QSize(100, 50); - p.anchorRect = QRect(QPoint(1, 2), QSize(800, 600)); - QVERIFY(p.isComplete()); + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); - p.anchorEdges = Qt::TopEdge | Qt::LeftEdge; - p.gravityEdges = Qt::BottomEdge | Qt::RightEdge; - QCOMPARE(p.unconstrainedPosition(), QPoint(1, 2)); + const QSize destinationSize(128, 123); + wp_viewport_set_destination(viewport, destinationSize.width(), destinationSize.height()); - p.anchorEdges = Qt::RightEdge; - QCOMPARE(p.unconstrainedPosition(), QPoint(1 + 800, 2 + 600 / 2)); + const QRectF sourceGeometry(QPointF(10, 20), QSizeF(30, 40)); + wp_viewport_set_source(viewport, + wl_fixed_from_double(sourceGeometry.x()), + wl_fixed_from_double(sourceGeometry.y()), + wl_fixed_from_double(sourceGeometry.width()), + wl_fixed_from_double(sourceGeometry.height())); - p.gravityEdges = Qt::BottomEdge; - QCOMPARE(p.unconstrainedPosition(), QPoint(1 + 800 - 100 / 2, 2 + 600 / 2)); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->bufferSize(), bufferSize); + QCOMPARE(waylandSurface->destinationSize(), destinationSize); + QCOMPARE(waylandSurface->sourceGeometry(), sourceGeometry); + + wp_viewport_destroy(viewport); + wl_surface_destroy(surface); + QCOMPARE(client.error, 0); +} - p.gravityEdges = Qt::TopEdge; - QCOMPARE(p.unconstrainedPosition(), QPoint(1 + 800 - 100 / 2, 2 + 600 / 2 - 50)); +void tst_WaylandCompositor::viewportDestruction() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + QCOMPARE(waylandSurface->destinationSize(), QSize()); + QCOMPARE(waylandSurface->sourceGeometry(), QRect()); + + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + + const QSize destinationSize(128, 123); + wp_viewport_set_destination(viewport, destinationSize.width(), destinationSize.height()); + + const QRectF sourceGeometry(QPointF(10, 20), QSizeF(30, 40)); + wp_viewport_set_source(viewport, + wl_fixed_from_double(sourceGeometry.x()), + wl_fixed_from_double(sourceGeometry.y()), + wl_fixed_from_double(sourceGeometry.width()), + wl_fixed_from_double(sourceGeometry.height())); + + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->bufferSize(), bufferSize); + QCOMPARE(waylandSurface->destinationSize(), destinationSize); + QCOMPARE(waylandSurface->sourceGeometry(), sourceGeometry); + + wp_viewport_destroy(viewport); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->destinationSize(), bufferSize); + QCOMPARE(waylandSurface->sourceGeometry(), QRectF(QPoint(), bufferSize)); + + wl_surface_destroy(surface); + QCOMPARE(client.error, 0); +} + +void tst_WaylandCompositor::viewportProtocolErrors_data() +{ + QTest::addColumn<QRectF>("source"); + QTest::addColumn<QSize>("destination"); + QTest::addColumn<uint>("error"); + + QTest::newRow("invalid source position") << QRectF(-1, 0, 16, 16) << QSize(64, 64) << uint(WP_VIEWPORT_ERROR_BAD_VALUE); + QTest::newRow("invalid source size") << QRectF(0, 0, -1, 16) << QSize(64, 64) << uint(WP_VIEWPORT_ERROR_BAD_VALUE); + QTest::newRow("invalid destination size") << QRectF(0, 0, 16, 16) << QSize(-16, 64) << uint(WP_VIEWPORT_ERROR_BAD_VALUE); + QTest::newRow("invalid non-integer source with unset size") << QRectF(0, 0, 15.5, 15.5) << QSize(-1, -1) << uint(WP_VIEWPORT_ERROR_BAD_SIZE); + QTest::newRow("bigger source than buffer") << QRectF(0, 0, 13337, 13337) << QSize(-1, -1) << uint(WP_VIEWPORT_ERROR_OUT_OF_BUFFER); +} + +void tst_WaylandCompositor::viewportProtocolErrors() +{ + QFETCH(QRectF, source); + QFETCH(QSize, destination); + QFETCH(uint, error); + + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + wp_viewport_set_source(viewport, + wl_fixed_from_double(source.x()), + wl_fixed_from_double(source.y()), + wl_fixed_from_double(source.width()), + wl_fixed_from_double(source.height())); + wp_viewport_set_destination(viewport, destination.width(), destination.height()); + wl_surface_commit(surface); + + QTRY_COMPARE(client.error, EPROTO); + QCOMPARE(client.protocolError.interface, &wp_viewport_interface); + QCOMPARE(static_cast<wp_viewport_error>(client.protocolError.code), error); +} + +void tst_WaylandCompositor::viewportClearDestination() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + QCOMPARE(waylandSurface->destinationSize(), QSize()); + QCOMPARE(waylandSurface->sourceGeometry(), QRect()); + + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + + const QSize destinationSize(128, 123); + wp_viewport_set_destination(viewport, destinationSize.width(), destinationSize.height()); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->bufferSize(), bufferSize); + QCOMPARE(waylandSurface->destinationSize(), destinationSize); + + wp_viewport_set_destination(viewport, -1, -1); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->destinationSize(), bufferSize); + QCOMPARE(waylandSurface->sourceGeometry(), QRectF(QPoint(), bufferSize)); + + wp_viewport_destroy(viewport); + wl_surface_destroy(surface); + QCOMPARE(client.error, 0); +} + +void tst_WaylandCompositor::viewportClearSource() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + QCOMPARE(waylandSurface->destinationSize(), QSize()); + QCOMPARE(waylandSurface->sourceGeometry(), QRect()); + + const QSize bufferSize(64, 64); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + QRectF source(10, 20, 30, 40); + wp_viewport_set_source(viewport, + wl_fixed_from_double(source.x()), + wl_fixed_from_double(source.y()), + wl_fixed_from_double(source.width()), + wl_fixed_from_double(source.height())); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->sourceGeometry(), source); + + wp_viewport_set_source(viewport, + wl_fixed_from_double(-1), + wl_fixed_from_double(-1), + wl_fixed_from_double(-1), + wl_fixed_from_double(-1)); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->sourceGeometry(), QRectF(QPoint(), bufferSize)); + + wp_viewport_destroy(viewport); + wl_surface_destroy(surface); + QCOMPARE(client.error, 0); +} + +void tst_WaylandCompositor::viewportExistsError() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + wp_viewporter_get_viewport(client.viewporter, surface); + wp_viewporter_get_viewport(client.viewporter, surface); + + QTRY_COMPARE(client.error, EPROTO); + QCOMPARE(client.protocolError.interface, &wp_viewporter_interface); + QCOMPARE(static_cast<wp_viewporter_error>(client.protocolError.code), WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS); +} + +void tst_WaylandCompositor::viewportDestinationNoSurfaceError() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + wl_surface_destroy(surface); + wp_viewport_set_destination(viewport, 32, 32); + + QTRY_COMPARE(client.error, EPROTO); + QCOMPARE(client.protocolError.interface, &wp_viewport_interface); + QCOMPARE(static_cast<wp_viewport_error>(client.protocolError.code), WP_VIEWPORT_ERROR_NO_SURFACE); +} + +void tst_WaylandCompositor::viewportSourceNoSurfaceError() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + wl_surface_destroy(surface); + wp_viewport_set_source(viewport, + wl_fixed_from_double(0), + wl_fixed_from_double(0), + wl_fixed_from_double(1), + wl_fixed_from_double(1)); + + QTRY_COMPARE(client.error, EPROTO); + QCOMPARE(client.protocolError.interface, &wp_viewport_interface); + QCOMPARE(static_cast<wp_viewport_error>(client.protocolError.code), WP_VIEWPORT_ERROR_NO_SURFACE); +} + +void tst_WaylandCompositor::viewportHiDpi() +{ + ViewporterTestCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.viewporter); + + wl_surface *surface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + + const QSize bufferSize(128, 128); + ShmBuffer buffer(bufferSize, client.shm); + wl_surface_attach(surface, buffer.handle, 0, 0); + wl_surface_damage(surface, 0, 0, bufferSize.width(), bufferSize.height()); + constexpr int bufferScale = 2; + wl_surface_set_buffer_scale(surface, bufferScale); + + wl_surface_commit(surface); + QTRY_COMPARE(waylandSurface->destinationSize(), bufferSize / bufferScale); + + wp_viewport *viewport = wp_viewporter_get_viewport(client.viewporter, surface); + const QRectF sourceGeometry(QPointF(10, 20), QSizeF(30, 40)); + wp_viewport_set_source(viewport, + wl_fixed_from_double(sourceGeometry.x()), + wl_fixed_from_double(sourceGeometry.y()), + wl_fixed_from_double(sourceGeometry.width()), + wl_fixed_from_double(sourceGeometry.height())); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->destinationSize(), sourceGeometry.size()); + QCOMPARE(waylandSurface->sourceGeometry(), sourceGeometry); + QCOMPARE(waylandSurface->bufferSize(), bufferSize); + + const QSize destinationSize(128, 123); + wp_viewport_set_destination(viewport, destinationSize.width(), destinationSize.height()); + wl_surface_commit(surface); + + QTRY_COMPARE(waylandSurface->destinationSize(), destinationSize); + QCOMPARE(waylandSurface->sourceGeometry(), sourceGeometry); + QCOMPARE(waylandSurface->bufferSize(), bufferSize); + + QCOMPARE(client.error, 0); + + wp_viewport_destroy(viewport); + wl_surface_destroy(surface); +} + +class IdleInhibitCompositor : public TestCompositor +{ + Q_OBJECT +public: + IdleInhibitCompositor() : idleInhibitManager(this) {} + QWaylandIdleInhibitManagerV1 idleInhibitManager; +}; + +void tst_WaylandCompositor::idleInhibit() +{ + IdleInhibitCompositor compositor; + compositor.create(); + MockClient client; + QTRY_VERIFY(client.idleInhibitManager); + + auto *surface = client.createSurface(); + QVERIFY(surface); + QTRY_COMPARE(compositor.surfaces.size(), 1); + + QWaylandSurface *waylandSurface = compositor.surfaces.at(0); + auto *waylandSurfacePrivate = + QWaylandSurfacePrivate::get(waylandSurface); + QVERIFY(waylandSurfacePrivate); + + QSignalSpy changedSpy(waylandSurface, SIGNAL(inhibitsIdleChanged())); + + QCOMPARE(waylandSurface->inhibitsIdle(), false); + + auto *idleInhibitor = client.createIdleInhibitor(surface); + QVERIFY(idleInhibitor); + QTRY_COMPARE(waylandSurfacePrivate->idleInhibitors.size(), 1); + QCOMPARE(waylandSurface->inhibitsIdle(), true); + QTRY_COMPARE(changedSpy.size(), 1); +} + +class XdgOutputCompositor : public TestCompositor +{ + Q_OBJECT +public: + XdgOutputCompositor() : xdgOutputManager(this) {} + QWaylandXdgOutputManagerV1 xdgOutputManager; +}; + +void tst_WaylandCompositor::xdgOutput() +{ + XdgOutputCompositor compositor; + compositor.create(); + + QWaylandOutputMode mode(QSize(1024, 768), 60000); + compositor.defaultOutput()->addMode(mode, true); + compositor.defaultOutput()->setCurrentMode(mode); + + MockClient client; + QTRY_VERIFY(client.xdgOutputManager); + QTRY_COMPARE(client.m_outputs.size(), 1); + + auto *wlOutput = client.m_outputs.first(); + QVERIFY(wlOutput); + + // Output is not associated yet + QCOMPARE(QWaylandOutputPrivate::get(compositor.defaultOutput())->xdgOutput.isNull(), true); + + // Create xdg-output on the server + auto *xdgOutputServer = new QWaylandXdgOutputV1(compositor.defaultOutput(), &compositor.xdgOutputManager); + QVERIFY(xdgOutputServer); + xdgOutputServer->setName(QStringLiteral("OUTPUT1")); + xdgOutputServer->setDescription(QStringLiteral("This is a test output")); + + // Create it on the client + auto *xdgOutput = client.createXdgOutput(wlOutput); + QVERIFY(xdgOutput); + QVERIFY(client.m_xdgOutputs.contains(wlOutput)); + + // Now it should be associated + QCOMPARE(QWaylandOutputPrivate::get(compositor.defaultOutput())->xdgOutput.isNull(), false); + + // Verify initial values + QTRY_COMPARE(xdgOutput->name, "OUTPUT1"); + QTRY_COMPARE(xdgOutput->logicalPosition, QPoint()); + QTRY_COMPARE(xdgOutput->logicalSize, QSize()); + + // Change properties + xdgOutputServer->setName(QStringLiteral("OUTPUT2")); + xdgOutputServer->setDescription(QStringLiteral("New description")); + xdgOutputServer->setLogicalPosition(QPoint(100, 100)); + xdgOutputServer->setLogicalSize(QSize(1000, 1000)); + compositor.flushClients(); - p.offset = QPoint(4, 8); - QCOMPARE(p.unconstrainedPosition(), QPoint(1 + 800 - 100 / 2 + 4, 2 + 600 / 2 - 50 + 8)); + // Name and description can't be changed after initialization, + // so we expect them to be the same + // TODO: With protocol version 3 the description will be allowed to change, + // but we implement version 2 now + QTRY_COMPARE(xdgOutput->name, "OUTPUT1"); + QTRY_COMPARE(xdgOutput->description, "This is a test output"); + QTRY_COMPARE(xdgOutput->logicalPosition, QPoint(100, 100)); + QTRY_COMPARE(xdgOutput->logicalSize, QSize(1000, 1000)); } #include <tst_compositor.moc> diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt new file mode 100644 index 000000000..87ae459fd --- /dev/null +++ b/tests/manual/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(TARGET Qt::WaylandClient) + add_subdirectory(qmlclient) + add_subdirectory(subsurface) + add_subdirectory(texture-sharing/cpp-client) + add_subdirectory(texture-sharing-2) +endif() + +if(TARGET Qt::WaylandCompositor) + #add_subdirectory(wip-cpp-compositor) + add_subdirectory(scaling-compositor) + add_subdirectory(hwlayer-compositor) +endif() + +if(QT_FEATURE_opengl AND TARGET Qt::Quick AND TARGET Qt::WaylandClient) + add_subdirectory(server-buffer) +endif() diff --git a/tests/manual/hwlayer-compositor/.gitignore b/tests/manual/hwlayer-compositor/.gitignore new file mode 100644 index 000000000..83a421caf --- /dev/null +++ b/tests/manual/hwlayer-compositor/.gitignore @@ -0,0 +1 @@ +hwlayer-compositor diff --git a/tests/manual/hwlayer-compositor/CMakeLists.txt b/tests/manual/hwlayer-compositor/CMakeLists.txt new file mode 100644 index 000000000..729bae99b --- /dev/null +++ b/tests/manual/hwlayer-compositor/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(hwlayer-compositor LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/wayland/hwlayer-compositor") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml) + +qt_add_executable(hwlayer-compositor + main.cpp +) + +set_target_properties(hwlayer-compositor PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(hwlayer-compositor PUBLIC + Qt::Core + Qt::Gui + Qt::Qml +) + +# Resources: +set(hwlayer-compositor_resource_files + "main.qml" +) + +qt6_add_resources(hwlayer-compositor "hwlayer-compositor" + PREFIX + "/" + FILES + ${hwlayer-compositor_resource_files} +) + +install(TARGETS hwlayer-compositor + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/tests/manual/hwlayer-compositor/hwlayer-compositor.pro b/tests/manual/hwlayer-compositor/hwlayer-compositor.pro new file mode 100644 index 000000000..a6eed9079 --- /dev/null +++ b/tests/manual/hwlayer-compositor/hwlayer-compositor.pro @@ -0,0 +1,14 @@ +QT += gui qml + +SOURCES += \ + main.cpp + +OTHER_FILES = \ + main.qml + +RESOURCES += hwlayer-compositor.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/hwlayer-compositor +sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS hwlayer-compositor.pro +sources.path = $$[QT_INSTALL_EXAMPLES]/wayland/hwlayer-compositor +INSTALLS += target sources diff --git a/tests/manual/hwlayer-compositor/hwlayer-compositor.qrc b/tests/manual/hwlayer-compositor/hwlayer-compositor.qrc new file mode 100644 index 000000000..5f6483ac3 --- /dev/null +++ b/tests/manual/hwlayer-compositor/hwlayer-compositor.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>main.qml</file> + </qresource> +</RCC> diff --git a/tests/manual/hwlayer-compositor/main.cpp b/tests/manual/hwlayer-compositor/main.cpp new file mode 100644 index 000000000..85cab3cec --- /dev/null +++ b/tests/manual/hwlayer-compositor/main.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QUrl> +#include <QtCore/QDebug> + +#include <QtGui/QGuiApplication> +#include <QQmlContext> + +#include <QtQml/QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + QQmlApplicationEngine appEngine(QUrl("qrc:///main.qml")); + return app.exec(); +} diff --git a/tests/manual/hwlayer-compositor/main.qml b/tests/manual/hwlayer-compositor/main.qml new file mode 100644 index 000000000..32a197ac1 --- /dev/null +++ b/tests/manual/hwlayer-compositor/main.qml @@ -0,0 +1,117 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtWayland.Compositor +import QtWayland.Compositor.XdgShell +import QtWayland.Compositor.WlShell +import QtWayland.Compositor.IviApplication + +WaylandCompositor { + WaylandOutput { + sizeFollowsWindow: true + window: Window { + color: "tomato" + id: win + width: 1024 + height: 768 + visible: true + Rectangle { + color: "lightgreen" + anchors.centerIn: parent + width: parent.width / 3 + height: parent.width / 3 + NumberAnimation on rotation { + id: rotationAnimation + running: false + from: 0 + to: 90 + loops: Animation.Infinite + duration: 1000 + } + } + Repeater { + model: shellSurfaces + ShellSurfaceItem { + id: waylandItem + onSurfaceDestroyed: shellSurfaces.remove(index) + shellSurface: shSurface + WaylandHardwareLayer { + stackingLevel: level + Component.onCompleted: console.log("Added hardware layer with stacking level", stackingLevel); + } + Component.onCompleted: console.log("Added wayland quick item"); + Behavior on x { + PropertyAnimation { + easing.type: Easing.OutBounce + duration: 1000 + } + } + Timer { + interval: 2000; running: animatePosition; repeat: true + onTriggered: waylandItem.x = waylandItem.x === 0 ? win.width - waylandItem.width : 0 + } + Behavior on opacity { + PropertyAnimation { + duration: 1000 + } + } + Timer { + interval: 2000; running: animateOpacity; repeat: true + onTriggered: waylandItem.opacity = waylandItem.opacity === 1 ? 0 : 1 + } + } + } + Column { + anchors.bottom: parent.bottom + Repeater { + model: shellSurfaces + Row { + Label { + anchors.verticalCenter: parent.verticalCenter + leftPadding: 15 + rightPadding: 15 + text: "Surface " + index + } + CheckBox { + text: "Animate position" + checked: animatePosition + onClicked: animatePosition = !animatePosition + } + CheckBox { + text: "Animate Opacity" + checked: animateOpacity + onClicked: animateOpacity = !animateOpacity + } + Label { + text: "Stacking level" + } + SpinBox { + value: level + onValueModified: level = value; + } + Button { + text: "Kill" + onClicked: shSurface.surface.client.kill() + } + } + } + CheckBox { + text: "Rotation" + checked: rotationAnimation.running + onClicked: rotationAnimation.running = !rotationAnimation.running + padding: 30 + } + } + } + } + ListModel { id: shellSurfaces } + function addShellSurface(shellSurface) { + shellSurfaces.append({shSurface: shellSurface, animatePosition: false, animateOpacity: false, level: 0}); + } + XdgShell { onToplevelCreated: (toplevel, xdgSurface) => addShellSurface(xdgSurface) } + IviApplication { onIviSurfaceCreated: (iviSurface) => addShellSurface(iviSurface) } + WlShell { onWlShellSurfaceCreated: (shellSurface) => addShellSurface(shellSurface) } +} diff --git a/tests/manual/import-qml-modules/CMakeLists.txt b/tests/manual/import-qml-modules/CMakeLists.txt new file mode 100644 index 000000000..c99218203 --- /dev/null +++ b/tests/manual/import-qml-modules/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +project(import_wayland_qml_modules VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.4 REQUIRED + COMPONENTS + Quick + WaylandCompositor + WaylandCompositorIviapplication + WaylandCompositorPresentationTime + WaylandCompositorWLShell + WaylandCompositorXdgShell +) + +qt_standard_project_setup() + +qt_add_qml_module(import_wayland_qml_modules + URI Qml + QML_FILES Main.qml +) diff --git a/tests/manual/import-qml-modules/Main.qml b/tests/manual/import-qml-modules/Main.qml new file mode 100644 index 000000000..07b8bc482 --- /dev/null +++ b/tests/manual/import-qml-modules/Main.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtWayland.Compositor.IviApplication +import QtWayland.Compositor.PresentationTime +import QtWayland.Compositor.QtShell +import QtWayland.Compositor.WlShell +import QtWayland.Compositor.XdgShell + + +Item { + property var p1: IviApplication {} + property var p2: PresentationTime {} + property var p3: QtShellChrome {} + property var p4: WlShellSurface {} + property var p5: XdgPopup {} +} diff --git a/tests/manual/import-qml-modules/main.cpp b/tests/manual/import-qml-modules/main.cpp new file mode 100644 index 000000000..1b0b801ff --- /dev/null +++ b/tests/manual/import-qml-modules/main.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtGui/QGuiApplication> +#include <QtQml/QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + const QUrl url(QStringLiteral("qrc:/Qml/Main.qml")); + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/tests/manual/keymap/keymapcompositor.qml b/tests/manual/keymap/keymapcompositor.qml index 77111c815..7d8bcda7c 100644 --- a/tests/manual/keymap/keymapcompositor.qml +++ b/tests/manual/keymap/keymapcompositor.qml @@ -1,55 +1,9 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.0 import QtWayland.Compositor 1.0 +import QtWayland.Compositor.WlShell import QtQuick.Window 2.2 WaylandCompositor { @@ -91,7 +45,4 @@ WaylandCompositor { WlShell { onWlShellSurfaceCreated: chromeComponent.createObject(surfaceArea, { "shellSurface": shellSurface } ); } - XdgShellV5 { - onXdgSurfaceCreated: chromeComponent.createObject(surfaceArea, { "shellSurface": xdgSurface } ); - } } diff --git a/tests/manual/qmlclient/CMakeLists.txt b/tests/manual/qmlclient/CMakeLists.txt new file mode 100644 index 000000000..7dedc67bf --- /dev/null +++ b/tests/manual/qmlclient/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from qmlclient.pro. + +##################################################################### +## qmlclient Binary: +##################################################################### + +qt_internal_add_manual_test(qmlclient + GUI + SOURCES + main.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + Qt::Quick + Qt::WaylandClient +) + +# Resources: +set(qml_resource_files + "main.qml" +) + +qt_internal_add_resource(qmlclient "qml" + PREFIX + "/" + FILES + ${qml_resource_files} +) + + +#### Keys ignored in scope 1:.:.:qmlclient.pro:<TRUE>: +# TEMPLATE = "app" diff --git a/tests/manual/qmlclient/main.cpp b/tests/manual/qmlclient/main.cpp index 9e0774b13..673359b52 100644 --- a/tests/manual/qmlclient/main.cpp +++ b/tests/manual/qmlclient/main.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtGui/QGuiApplication> #include <QtQml/QQmlApplicationEngine> diff --git a/tests/manual/qmlclient/main.qml b/tests/manual/qmlclient/main.qml index 5ee63955b..b5061c182 100644 --- a/tests/manual/qmlclient/main.qml +++ b/tests/manual/qmlclient/main.qml @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.2 import QtQuick.Window 2.2 diff --git a/tests/manual/qt-shell/CMakeLists.txt b/tests/manual/qt-shell/CMakeLists.txt new file mode 100644 index 000000000..daf8fb25d --- /dev/null +++ b/tests/manual/qt-shell/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.14) +project(qt-shell LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 COMPONENTS Core) +find_package(Qt6 COMPONENTS Gui) +find_package(Qt6 COMPONENTS Qml) + +qt_add_executable(qt-shell + main.cpp +) +set_target_properties(qt-shell PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) +target_link_libraries(qt-shell PUBLIC + Qt::Core + Qt::Gui + Qt::Qml +) + + +# Resources: +set(qt-shell_resource_files + "images/background.jpg" + "qml/Chrome.qml" + "qml/HandleHandler.qml" + "qml/CompositorScreen.qml" + "qml/Keyboard.qml" + "qml/main.qml" +) + +qt6_add_resources(qt-shell "qt-shell" + PREFIX + "/" + FILES + ${qt-shell_resource_files} +) diff --git a/tests/manual/qt-shell/images/background.jpg b/tests/manual/qt-shell/images/background.jpg Binary files differnew file mode 100644 index 000000000..445567fbd --- /dev/null +++ b/tests/manual/qt-shell/images/background.jpg diff --git a/tests/manual/qt-shell/main.cpp b/tests/manual/qt-shell/main.cpp new file mode 100644 index 000000000..31bd58fae --- /dev/null +++ b/tests/manual/qt-shell/main.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QUrl> +#include <QtCore/QDebug> + +#include <QtGui/QGuiApplication> + +#include <QtQml/QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + // ShareOpenGLContexts is needed for using the threaded renderer + // on Nvidia EGLStreams + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine appEngine(QUrl("qrc:///qml/main.qml")); + + return app.exec(); +} diff --git a/tests/manual/qt-shell/qml/Chrome.qml b/tests/manual/qt-shell/qml/Chrome.qml new file mode 100644 index 000000000..9eb2f1965 --- /dev/null +++ b/tests/manual/qt-shell/qml/Chrome.qml @@ -0,0 +1,458 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtWayland.Compositor + +Item { + id: chrome + + property bool positionSet: false + + x: shellSurface.windowGeometry.x - leftResizeHandle.width + y: shellSurface.windowGeometry.y - topResizeHandle.height - titleBar.height + width: shellSurface.windowGeometry.width + leftResizeHandle.width + rightResizeHandle.width + height: shellSurface.windowGeometry.height + topResizeHandle.height + titleBar.height + bottomResizeHandle.height + + property rect oldGeometry: Qt.rect(0, 0, 100, 100) + property bool isChild: parent.shellSurface !== undefined + property alias shellSurface: shellSurfaceItem.shellSurface + + property int windowState: Qt.WindowNoState + + signal destroyAnimationFinished + signal activated + signal deactivated + + property int windowFlags: shellSurface.windowFlags !== Qt.Window + ? shellSurface.windowFlags + : defaultFlags + onDecorationsShowingChanged:{ + shellSurfaceItem.updateFrameMargins() + } + + Component.onCompleted: { + shellSurface.active = true + } + + property int defaultFlags: (Qt.Window + | Qt.WindowMaximizeButtonHint + | Qt.WindowMinimizeButtonHint + | Qt.WindowCloseButtonHint) + + property bool frameless: (chrome.windowFlags & Qt.FramelessWindowHint) != 0 + || (chrome.windowState & Qt.WindowFullScreen) != 0 + || ((chrome.windowFlags & Qt.Popup) == Qt.Popup + && (chrome.windowFlags & Qt.Tool) != Qt.Tool) + + property bool decorationsShowing: (chrome.windowFlags & Qt.Window) != 0 && !frameless + + transform: [ + Scale { + id: scaleTransform + origin.x: chrome.width / 2 + origin.y: chrome.height / 2 + } + ] + + Rectangle { + id: leftResizeHandle + color: "gray" + width: visible ? 5 : 0 + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: westBound + } + } + + Rectangle { + id: rightResizeHandle + color: "gray" + width: visible ? 5 : 0 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: eastBound + } + } + + Rectangle { + id: topResizeHandle + color: "gray" + height: visible ? 5 : 0 + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: northBound + } + } + + Rectangle { + id: bottomResizeHandle + color: "gray" + height: visible ? 5 : 0 + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.right: parent.right + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: southBound + } + } + + Rectangle { + id: topLeftResizeHandle + color: "gray" + height: 5 + width: 5 + anchors.left: parent.left + anchors.top: parent.top + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: westBound | northBound + } + } + + Rectangle { + id: topRightResizeHandle + color: "gray" + height: 5 + width: 5 + anchors.right: parent.right + anchors.top: parent.top + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: eastBound | northBound + } + } + + Rectangle { + id: bottomLeftResizeHandle + color: "gray" + height: 5 + width: 5 + anchors.left: parent.left + anchors.bottom: parent.bottom + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: westBound | southBound + } + } + + Rectangle { + id: bottomRightResizeHandle + color: "gray" + height: 5 + width: 5 + anchors.right: parent.right + anchors.bottom: parent.bottom + visible: decorationsShowing + + HandleHandler { + enabled: (chrome.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + flags: eastBound | southBound + } + } + + function constrainPoint(mousePos) { + var x0 = usableArea.x + var y0 = usableArea.y + var x1 = x0 + usableArea.width + var y1 = y0 + usableArea.height + return Qt.point(Math.min(Math.max(x0,mousePos.x), x1), + Math.min(Math.max(y0,mousePos.y), y1)) + } + + function maxContentRect() { + var x0 = usableArea.x + leftResizeHandle.width + var x1 = usableArea.x + usableArea.width - rightResizeHandle.width + var y0 = usableArea.y + topResizeHandle.height + titleBar.height + var y1 = usableArea.y + usableArea.height - bottomResizeHandle.height + return Qt.rect(x0, y0, x1 - x0, y1 - y0) + } + + function randomPos(windowSize, screenSize) { + var res = (windowSize >= screenSize) ? 0 : Math.floor(Math.random() * (screenSize - windowSize)) + return res + } + + function activate() + { + shellSurface.active = true + shellSurfaceItem.raise() + activated() + } + + function deactivate() + { + shellSurface.active = true + deactivated() + } + + function setWindowState(nextState) { + var currentState = chrome.windowState + if (currentState === nextState) + return + + console.log("setWindowState", nextState.toString(16)) + + if ((currentState & (Qt.WindowMinimized | Qt.WindowMaximized | Qt.WindowFullScreen)) == 0) + chrome.oldGeometry = chrome.shellSurface.windowGeometry + + chrome.windowState = nextState + + if ((nextState & Qt.WindowMinimized) != 0) { + console.log("MINIMIZE") + chrome.shellSurface.requestWindowGeometry(nextState, Qt.rect(0, 0, 1, 1)) + shellSurfaceItem.visible = false + } else if ((nextState & Qt.WindowFullScreen) != 0) { + console.log("FULLSCREENIZE") + chrome.shellSurface.requestWindowGeometry(nextState, Qt.rect(0, 0, output.window.width, output.window.height)) + shellSurfaceItem.visible = true + } else if ((nextState & Qt.WindowMaximized) != 0) { + console.log("MAXIMIZE") + chrome.shellSurface.requestWindowGeometry(nextState, maxContentRect()) + shellSurfaceItem.visible = true + } else { + console.log("NORMALIZE", chrome.oldGeometry) + chrome.shellSurface.requestWindowGeometry(nextState, chrome.oldGeometry) + shellSurfaceItem.visible = true + } + } + + Rectangle { + id: titleBar + anchors.top: topResizeHandle.bottom + anchors.left: leftResizeHandle.right + anchors.right: rightResizeHandle.left + height: visible ? xButton.height + 10 : 0 + color: shellSurface.active ? "cornflowerblue" : "lightgray" + visible: !frameless + + Text { + anchors.left: parent.left + anchors.right: rowLayout.left + anchors.verticalCenter: parent.verticalCenter + + font.pixelSize: xButton.height + text: shellSurface.windowTitle + fontSizeMode: Text.Fit + } + + RowLayout { + id: rowLayout + anchors.right: parent.right + anchors.rightMargin: 5 + + ToolButton { + text: "-" + Layout.margins: 5 + visible: (chrome.windowFlags & Qt.WindowMinimizeButtonHint) != 0 + onClicked: { + var newState + if ((shellSurface.windowState & Qt.WindowMinimized) != 0) + newState = chrome.windowState & ~Qt.WindowMinimized + else + newState = chrome.windowState | Qt.WindowMinimized + + if ((newState & Qt.WindowMaximized) != 0) + newState &= ~Qt.WindowMaximized + + setWindowState(newState) + } + } + + ToolButton { + text: "+" + Layout.margins: 5 + visible: (chrome.windowFlags & Qt.WindowMaximizeButtonHint) != 0 + onClicked: { + var newState + if ((shellSurface.windowState & Qt.WindowMaximized) != 0) + newState = shellSurface.windowState & ~Qt.WindowMaximized + else + newState = shellSurface.windowState | Qt.WindowMaximized + + if ((newState & Qt.WindowMinimized) != 0) + newState &= ~Qt.WindowMinimized + + setWindowState(newState) + } + } + + ToolButton { + id: xButton + text: "X" + Layout.margins: 5 + visible: (chrome.windowFlags & Qt.WindowCloseButtonHint) != 0 + onClicked: shellSurface.sendClose() + } + } + + DragHandler { + target: null + property real xOffset: -1.0 + property real yOffset: -1.0 + property bool started: false + enabled: (shellSurface.windowState & (Qt.WindowMinimized|Qt.WindowMaximized)) == 0 + + onGrabChanged: { + started = false + activate() + } + + onCentroidChanged: { + if (!active) + return + + if (!started) { + xOffset = shellSurface.windowPosition.x - centroid.scenePressPosition.x + yOffset = shellSurface.windowPosition.y - centroid.scenePressPosition.y + started = true + chrome.positionAutomatic = false + } + + var pos = chrome.constrainPoint(centroid.scenePosition) + shellSurface.windowPosition = Qt.point(pos.x + xOffset, pos.y + yOffset) + } + } + } + + ShellSurfaceItem { + id: shellSurfaceItem + anchors.top: titleBar.bottom + anchors.bottom: bottomResizeHandle.top + anchors.left: leftResizeHandle.right + anchors.right: rightResizeHandle.left + + moveItem: chrome + + staysOnBottom: shellSurface.windowFlags & Qt.WindowStaysOnBottomHint + staysOnTop: !staysOnBottom && shellSurface.windowFlags & Qt.WindowStaysOnTopHint + function updateFrameMargins() + { + shellSurface.frameMarginLeft = (decorationsShowing ? leftResizeHandle.width : 0) + shellSurface.frameMarginRight = (decorationsShowing ? rightResizeHandle.width : 0) + shellSurface.frameMarginTop = (decorationsShowing ? topResizeHandle.height : 0) + + (!frameless ? titleBar.height : 0) + shellSurface.frameMarginBottom = (decorationsShowing ? bottomResizeHandle.height : 0) + } + + Component.onCompleted: { + updateFrameMargins() + } + + onSurfaceDestroyed: { + bufferLocked = true; + destroyAnimation.start(); + } + + Connections { + target: shellSurface + function onWindowFlagsChanged() { + console.log("FLAGS", shellSurface.windowFlags.toString(16)) + shellSurfaceItem.updateFrameMargins() + } + + function onWindowStateChanged() { + setWindowState(shellSurface.windowState) + } + + function onActiveChanged() { + if (shellSurface.active) { + shellSurfaceItem.raise() + activated() + } else { + deactivated() + } + } + + function onStartResize() { + console.log("START SYSTEM RESIZE") + } + function onStartMove() { + console.log("START SYSTEM MOVE") + } + + function onRaiseRequested() { + console.log("RAISE") + shellSurfaceItem.raise() + } + function onLowerRequested() { + console.log("LOWER") + shellSurfaceItem.lower() + } + + function onWindowGeometryChanged() { + console.log("GEOM CHANGE", shellSurface.windowGeometry) + } + } + + Connections { + target: shellSurface.surface + function onHasContentChanged() { + if (!chrome.positionSet) { + var rect = shellSurface.windowGeometry + var w = rect.width + var h = rect.height + + var space = maxContentRect() + + var randomize = shellSurface.positionAutomatic + var xpos = randomize ? randomPos(w, space.width) + space.x : Math.max(rect.x, space.x) + var ypos = randomize ? randomPos(h, space.height) + space.y : Math.max(rect.y, space.y) + shellSurface.windowPosition = Qt.point(xpos, ypos) + } + chrome.positionSet = true + } + } + + SequentialAnimation { + id: destroyAnimation + + ParallelAnimation { + NumberAnimation { target: scaleTransform; property: "yScale"; to: 2/height; duration: 150 } + NumberAnimation { target: scaleTransform; property: "xScale"; to: 0.4; duration: 150 } + NumberAnimation { target: chrome; property: "opacity"; to: chrome.isChild ? 0 : 1; duration: 150 } + } + NumberAnimation { target: scaleTransform; property: "xScale"; to: 0; duration: 150 } + ScriptAction { script: chrome.destroyAnimationFinished() } + } + + SequentialAnimation { + id: receivedFocusAnimation + + ParallelAnimation { + NumberAnimation { target: scaleTransform; property: "yScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad } + NumberAnimation { target: scaleTransform; property: "xScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad } + } + ParallelAnimation { + NumberAnimation { target: scaleTransform; property: "yScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad } + NumberAnimation { target: scaleTransform; property: "xScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad } + } + } + } +} diff --git a/tests/manual/qt-shell/qml/CompositorScreen.qml b/tests/manual/qt-shell/qml/CompositorScreen.qml new file mode 100644 index 000000000..92b75b348 --- /dev/null +++ b/tests/manual/qt-shell/qml/CompositorScreen.qml @@ -0,0 +1,144 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtWayland.Compositor + +WaylandOutput { + id: output + + property ListModel shellSurfaces: ListModel {} + property bool isNestedCompositor: Qt.platform.pluginName.startsWith("wayland") || Qt.platform.pluginName === "xcb" + property int currentActiveWindow: -1 + + function handleShellSurface(shellSurface) { + shellSurfaces.append({shellSurface: shellSurface}); + } + + // During development, it can be useful to start the compositor inside X11 or + // another Wayland compositor. In such cases, set sizeFollowsWindow to true to + // enable resizing of the compositor window to be forwarded to the Wayland clients + // as the output (screen) changing resolution. Consider setting it to false if you + // are running the compositor using eglfs, linuxfb or similar QPA backends. + sizeFollowsWindow: output.isNestedCompositor + + window: Window { + width: 1920 + height: 1080 + visible: true + + WaylandMouseTracker { + id: mouseTracker + + anchors.fill: parent + + // Set this to false to disable the outer mouse cursor when running nested + // compositors. Otherwise you would see two mouse cursors, one for each compositor. + windowSystemCursorEnabled: output.isNestedCompositor + + Image { + id: background + + anchors.fill: parent + fillMode: Image.Tile + source: "qrc:/images/background.jpg" + smooth: true + + Repeater { + id: chromeRepeater + model: output.shellSurfaces + // Chrome displays a shell surface on the screen (See Chrome.qml) + Chrome { + shellSurface: modelData + onDestroyAnimationFinished: + { + if (currentActiveWindow > index) { + --currentActiveWindow + } else if (currentActiveWindow === index) { + currentActiveWindow = index - 1 + if (currentActiveWindow >= 0) { + var nextActiveSurface = output.shellSurfaces.get(currentActiveWindow).shellSurface + if (nextActiveSurface !== undefined) // More than one surface can get destroyed at the same time + nextActiveSurface.active = true + } + } + output.shellSurfaces.remove(index) + } + + onDeactivated: { + if (index === currentActiveWindow) + currentActiveWindow = -1 + } + + onActivated: { + if (index !== currentActiveWindow && currentActiveWindow >= 0) { + // This may already have been destroyed + if (output.shellSurfaces.get(currentActiveWindow).shellSurface !== undefined) + output.shellSurfaces.get(currentActiveWindow).shellSurface.active = false + } + + currentActiveWindow = index + } + } + } + } + + Rectangle { + anchors.fill: taskbar + color: "lavenderblush" + } + + Row { + id: taskbar + height: 40 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + Repeater { + anchors.fill: parent + model: output.shellSurfaces + + ToolButton { + anchors.verticalCenter: parent.verticalCenter + text: modelData.windowTitle + onClicked: { + modelData.requestWindowGeometry(modelData.windowState & ~Qt.WindowMinimized, + modelData.windowGeometry) + chromeRepeater.itemAt(index).activate() + } + } + } + } + + Item { + id: usableArea + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: taskbar.top + } + + // Virtual Keyboard + Loader { + anchors.fill: parent + source: "Keyboard.qml" + } + + // Draws the mouse cursor for a given Wayland seat + WaylandCursorItem { + inputEventsEnabled: false + x: mouseTracker.mouseX + y: mouseTracker.mouseY + seat: output.compositor.defaultSeat + } + } + + Shortcut { + sequence: "Ctrl+Alt+Backspace" + onActivated: Qt.quit() + } + } +} diff --git a/tests/manual/qt-shell/qml/HandleHandler.qml b/tests/manual/qt-shell/qml/HandleHandler.qml new file mode 100644 index 000000000..7b31ad4d5 --- /dev/null +++ b/tests/manual/qt-shell/qml/HandleHandler.qml @@ -0,0 +1,86 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +DragHandler { + target: null + + function selectCursor(f) + { + switch (f) { + case (southBound | eastBound): + case (northBound | westBound): + return Qt.SizeFDiagCursor + case (southBound | westBound): + case (northBound | eastBound): + return Qt.SizeBDiagCursor + case westBound: + case eastBound: + return Qt.SizeHorCursor + default: + return Qt.SizeVerCursor + } + } + + property int flags: WestBound + readonly property int westBound: 1 + readonly property int eastBound: 2 + readonly property int northBound: 4 + readonly property int southBound: 8 + + cursorShape: selectCursor(flags) + + property rect geom + property real startX: -1.0 + property real startY: -1.0 + property bool started: false + onGrabChanged: { + started = false + } + onCentroidChanged: { + if (!active) + return + if (!started) { + geom = shellSurface.windowGeometry + startX = centroid.scenePressPosition.x + startY = centroid.scenePressPosition.y + started = true + } + + var pos = chrome.constrainPoint(centroid.scenePosition) + var dx = pos.x - startX + var dy = pos.y - startY + + var minWidth = Math.max(0, shellSurface.minimumSize.width) + var minHeight = Math.max(0, shellSurface.minimumSize.height) + + var maxWidth = shellSurface.maximumSize.width > 0 ? shellSurface.maximumSize.width : Number.MAX_VALUE + var maxHeight = shellSurface.maximumSize.height > 0 ? shellSurface.maximumSize.height : Number.MAX_VALUE + + var newLeft = geom.left + if (flags & westBound) + newLeft = Math.max(geom.right - maxWidth, Math.min(geom.right - minWidth, newLeft + dx)); + + var newTop = geom.top + if (flags & northBound) + newTop = Math.max(geom.bottom - maxHeight, Math.min(geom.bottom - minHeight, newTop + dy)); + + var newRight = geom.right + if (flags & eastBound) + newRight = Math.max(geom.left, newRight + dx); + + var newBottom = geom.bottom + if (flags & southBound) + newBottom = Math.max(geom.top, newBottom + dy); + + console.log("RESIZE HANDLER", shellSurface.windowGeometry, geom, dy, newTop) + + + shellSurface.requestWindowGeometry(shellSurface.windowState, + Qt.rect(newLeft, + newTop, + newRight - newLeft, + newBottom - newTop)) + } +} diff --git a/tests/manual/qt-shell/qml/Keyboard.qml b/tests/manual/qt-shell/qml/Keyboard.qml new file mode 100644 index 000000000..a6e1bec30 --- /dev/null +++ b/tests/manual/qt-shell/qml/Keyboard.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.VirtualKeyboard + +InputPanel { + visible: active + y: active ? parent.height - height : parent.height + anchors.left: parent.left + anchors.right: parent.right +} diff --git a/tests/manual/qt-shell/qml/main.qml b/tests/manual/qt-shell/qml/main.qml new file mode 100644 index 000000000..986e0a490 --- /dev/null +++ b/tests/manual/qt-shell/qml/main.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtWayland.Compositor +import QtWayland.Compositor.QtShell + +WaylandCompositor { + id: waylandCompositor + + CompositorScreen { id: screen; compositor: waylandCompositor } + + // Shell surface extension. Needed to provide a window concept for Wayland clients. + // I.e. requests and events for maximization, minimization, resizing, closing etc. + + QtShell { + onQtShellSurfaceCreated: screen.handleShellSurface(qtShellSurface) + } + + // Extension for Input Method (QT_IM_MODULE) support at compositor-side + QtTextInputMethodManager {} +} diff --git a/tests/manual/qt-shell/qt-shell.pro b/tests/manual/qt-shell/qt-shell.pro new file mode 100644 index 000000000..c7e2cd912 --- /dev/null +++ b/tests/manual/qt-shell/qt-shell.pro @@ -0,0 +1,13 @@ +QT += gui qml + +SOURCES += \ + main.cpp + +OTHER_FILES = \ + qml/main.qml \ + qml/CompositorScreen.qml \ + qml/Chrome.qml \ + qml/Keyboard.qml \ + images/background.jpg \ + +RESOURCES += qt-shell.qrc diff --git a/tests/manual/qt-shell/qt-shell.qrc b/tests/manual/qt-shell/qt-shell.qrc new file mode 100644 index 000000000..3120382fd --- /dev/null +++ b/tests/manual/qt-shell/qt-shell.qrc @@ -0,0 +1,10 @@ +<RCC> + <qresource prefix="/"> + <file>images/background.jpg</file> + <file>qml/main.qml</file> + <file>qml/CompositorScreen.qml</file> + <file>qml/Chrome.qml</file> + <file>qml/Keyboard.qml</file> + <file>qml/HandleHandler.qml</file> + </qresource> +</RCC> diff --git a/tests/manual/scaling-compositor/CMakeLists.txt b/tests/manual/scaling-compositor/CMakeLists.txt new file mode 100644 index 000000000..2ee7d96bc --- /dev/null +++ b/tests/manual/scaling-compositor/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from scaling-compositor.pro. + +##################################################################### +## scaling-compositor Binary: +##################################################################### + +qt_internal_add_manual_test(scaling-compositor + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Gui + Qt::Qml +) + +# Resources: +set(qml_resource_files + "main.qml" +) + +qt_internal_add_resource(scaling-compositor "qml" + PREFIX + "/" + FILES + ${qml_resource_files} +) + + +#### Keys ignored in scope 1:.:.:scaling-compositor.pro:<TRUE>: +# TEMPLATE = "app" diff --git a/tests/manual/scaling-compositor/main.cpp b/tests/manual/scaling-compositor/main.cpp index b5c7dd688..e972b9ca6 100644 --- a/tests/manual/scaling-compositor/main.cpp +++ b/tests/manual/scaling-compositor/main.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtGui/QGuiApplication> #include <QtQml/QQmlApplicationEngine> diff --git a/tests/manual/scaling-compositor/main.qml b/tests/manual/scaling-compositor/main.qml index 056af4e5b..f173de3a0 100644 --- a/tests/manual/scaling-compositor/main.qml +++ b/tests/manual/scaling-compositor/main.qml @@ -1,57 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -import QtQuick 2.2 +import QtQuick 2.15 import QtQuick.Window 2.2 -import QtQuick.Controls 2.0 -import QtWayland.Compositor 1.0 +import QtWayland.Compositor 1.3 +import QtWayland.Compositor.WlShell +import QtWayland.Compositor.XdgShell WaylandCompositor { id: comp @@ -66,16 +20,7 @@ WaylandCompositor { height: 500 visible: true title: "Scaling compositor x" + output.scaleFactor - Button { - id: incrementButton - text: "+" - onClicked: ++output.scaleFactor - } - Button { - text: "-" - onClicked: output.scaleFactor = Math.max(1, output.scaleFactor - 1) - anchors.left: incrementButton.right - } + Repeater { model: shellSurfaces ShellSurfaceItem { @@ -83,6 +28,33 @@ WaylandCompositor { onSurfaceDestroyed: shellSurfaces.remove(index); } } + + Rectangle { + id: incrementButton + color: "#c0f0d0" + Text { + text: "+" + } + width: 100 + height: 30 + TapHandler { + onTapped: ++output.scaleFactor + } + } + + Rectangle { + id: decrementButton + color: "#f0d0c0" + Text { + text: "-" + } + width: 100 + height: 30 + TapHandler { + onTapped: output.scaleFactor = Math.max(1, output.scaleFactor - 1) + } + anchors.left: incrementButton.right + } } } @@ -91,4 +63,8 @@ WaylandCompositor { WlShell { onWlShellSurfaceCreated: shellSurfaces.append({shellSurface: shellSurface}); } + XdgShell { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } } diff --git a/tests/manual/scaling-compositor/scaling-compositor.pro b/tests/manual/scaling-compositor/scaling-compositor.pro index 847e07ea7..200dc8c40 100644 --- a/tests/manual/scaling-compositor/scaling-compositor.pro +++ b/tests/manual/scaling-compositor/scaling-compositor.pro @@ -1,7 +1,6 @@ TEMPLATE = app -QT += gui qml quickcontrols2 - +QT += gui qml SOURCES += main.cpp RESOURCES += qml.qrc diff --git a/tests/manual/server-buffer/CMakeLists.txt b/tests/manual/server-buffer/CMakeLists.txt new file mode 100644 index 000000000..973063c71 --- /dev/null +++ b/tests/manual/server-buffer/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) +project(server-buffer) + +add_subdirectory(cpp-client) +add_subdirectory(compositor) diff --git a/tests/manual/server-buffer/README b/tests/manual/server-buffer/README new file mode 100644 index 000000000..ae76ca755 --- /dev/null +++ b/tests/manual/server-buffer/README @@ -0,0 +1,32 @@ +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. + +If you have the dmabuf-server buffer integration (and you are running Mesa) +then start the compositor with: + +$ QT_WAYLAND_SERVER_BUFFER_INTEGRATION=dmabuf-server ./compositor + + +Note: if you are running a compositor on an X11 desktop, you also need to +set QT_XCB_GL_INTEGRATION=xcb_egl as usual. + +The compositor broadcasts the name of the server buffer integration to +all clients through the hardware integration extension. Therefore, +all you need to do is to start the client with + +$ ./server-buffer-cpp-client -platform wayland + +The client will show all the buffers shared by the compositor. + +For testing on desktop, there is also a shared memory based server buffer +integration that works with any graphics hardware: + +$ QT_WAYLAND_SERVER_BUFFER_INTEGRATION=shm-emulation-server QT_XCB_GL_INTEGRATION=xcb_egl ./compositor + +Note: the shm-emulation-server integration does not actually share graphics +buffers, so it will not give any graphics memory savings. It is intended solely +for testing during development and should never be used in production. diff --git a/tests/manual/server-buffer/compositor/CMakeLists.txt b/tests/manual/server-buffer/compositor/CMakeLists.txt new file mode 100644 index 000000000..5f4fb0c51 --- /dev/null +++ b/tests/manual/server-buffer/compositor/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(compositor) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/wayland/server-buffer/compositor") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml WaylandCompositor) + +qt_add_executable(compositor + main.cpp + sharebufferextension.cpp sharebufferextension.h +) + +qt6_generate_wayland_protocol_server_sources(compositor + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../share-buffer.xml +) + +set_target_properties(compositor PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(compositor PUBLIC + Qt::Core + Qt::Gui + Qt::Qml + Qt::WaylandCompositorPrivate +) + +# Resources: +set(compositor_resource_files + "images/Siberischer_tiger_de_edit02.jpg" + "images/background.png" + "qml/main.qml" +) + +qt6_add_resources(compositor "compositor" + PREFIX + "/" + FILES + ${compositor_resource_files} +) + +install(TARGETS compositor + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/tests/manual/server-buffer/compositor/compositor.pro b/tests/manual/server-buffer/compositor/compositor.pro new file mode 100644 index 000000000..26334c63c --- /dev/null +++ b/tests/manual/server-buffer/compositor/compositor.pro @@ -0,0 +1,26 @@ +QT += core gui qml + +QT += waylandcompositor-private + +CONFIG += wayland-scanner +CONFIG += c++11 +SOURCES += \ + main.cpp \ + sharebufferextension.cpp + +OTHER_FILES = \ + qml/main.qml \ + images/background.jpg + +WAYLANDSERVERSOURCES += \ + ../share-buffer.xml + +RESOURCES += compositor.qrc + +TARGET = compositor + +HEADERS += \ + sharebufferextension.h + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/server-buffer/compositor +INSTALLS += target diff --git a/tests/manual/server-buffer/compositor/compositor.qrc b/tests/manual/server-buffer/compositor/compositor.qrc new file mode 100644 index 000000000..b50594b55 --- /dev/null +++ b/tests/manual/server-buffer/compositor/compositor.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>images/background.png</file> + <file>images/Siberischer_tiger_de_edit02.jpg</file> + <file>qml/main.qml</file> + </qresource> +</RCC> diff --git a/tests/manual/server-buffer/compositor/images/Siberischer_tiger_de_edit02.jpg b/tests/manual/server-buffer/compositor/images/Siberischer_tiger_de_edit02.jpg Binary files differnew file mode 100644 index 000000000..eb1b73f84 --- /dev/null +++ b/tests/manual/server-buffer/compositor/images/Siberischer_tiger_de_edit02.jpg diff --git a/tests/manual/server-buffer/compositor/images/Siberischer_tiger_de_edit02.txt b/tests/manual/server-buffer/compositor/images/Siberischer_tiger_de_edit02.txt new file mode 100644 index 000000000..3a26c00d3 --- /dev/null +++ b/tests/manual/server-buffer/compositor/images/Siberischer_tiger_de_edit02.txt @@ -0,0 +1,5 @@ +Image from https://en.wikipedia.org/wiki/File:Siberischer_tiger_de_edit02.jpg + +Author: S. Taheri, edited by Fir0002 + +License: Creative Commons Attribution-Share Alike 2.5 Generic diff --git a/tests/manual/server-buffer/compositor/images/background.png b/tests/manual/server-buffer/compositor/images/background.png Binary files differnew file mode 100644 index 000000000..292160cd8 --- /dev/null +++ b/tests/manual/server-buffer/compositor/images/background.png diff --git a/tests/manual/server-buffer/compositor/main.cpp b/tests/manual/server-buffer/compositor/main.cpp new file mode 100644 index 000000000..36e607e34 --- /dev/null +++ b/tests/manual/server-buffer/compositor/main.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QUrl> +#include <QtCore/QDebug> +#include <QtGui/QGuiApplication> +#include <QtQml/QQmlApplicationEngine> + +#include <QtQml/qqml.h> +#include <QtQml/QQmlEngine> + +#include "sharebufferextension.h" + +static void registerTypes() +{ + qmlRegisterType<ShareBufferExtensionQuickExtension>("io.qt.examples.sharebufferextension", 1, 0, "ShareBufferExtension"); +} + +int main(int argc, char *argv[]) +{ + // Make sure there is a valid OpenGL context in the main thread + qputenv("QSG_RENDER_LOOP", "basic"); + + QGuiApplication app(argc, argv); + registerTypes(); + QQmlApplicationEngine appEngine(QUrl("qrc:///qml/main.qml")); + + return app.exec(); +} diff --git a/tests/manual/server-buffer/compositor/qml/main.qml b/tests/manual/server-buffer/compositor/qml/main.qml new file mode 100644 index 000000000..4227584af --- /dev/null +++ b/tests/manual/server-buffer/compositor/qml/main.qml @@ -0,0 +1,43 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtWayland.Compositor +import QtWayland.Compositor.WlShell +import QtQuick.Window + +import io.qt.examples.sharebufferextension + +WaylandCompositor { + WaylandOutput { + sizeFollowsWindow: true + window: Window { + width: 1024 + height: 768 + visible: true + Image { + id: surfaceArea + anchors.fill: parent + fillMode: Image.Tile + source: "qrc:/images/background.png" + smooth: false + } + } + } + + Component { + id: chromeComponent + ShellSurfaceItem { + onSurfaceDestroyed: destroy() + } + } + + WlShell { + onWlShellSurfaceCreated: (shellSurface) => { + chromeComponent.createObject(surfaceArea, { "shellSurface": shellSurface } ); + } + } + + ShareBufferExtension { + } +} diff --git a/tests/manual/server-buffer/compositor/sharebufferextension.cpp b/tests/manual/server-buffer/compositor/sharebufferextension.cpp new file mode 100644 index 000000000..bc8b01af3 --- /dev/null +++ b/tests/manual/server-buffer/compositor/sharebufferextension.cpp @@ -0,0 +1,86 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "sharebufferextension.h" + +#include <QWaylandSurface> + +#include <QDebug> + +#include <QQuickWindow> + +#include <QPainter> +#include <QPen> + +ShareBufferExtension::ShareBufferExtension(QWaylandCompositor *compositor) + : QWaylandCompositorExtensionTemplate(compositor) +{ +} + +void ShareBufferExtension::initialize() +{ + QWaylandCompositorExtensionTemplate::initialize(); + QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer()); + init(compositor->display(), 1); +} + +QtWayland::ServerBuffer *ShareBufferExtension::addImage(const QImage &img) +{ + 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("Could not find a server buffer integration"); + return nullptr; + } + } + + QImage image = img.convertToFormat(QImage::Format_RGBA8888); + + auto *buffer = m_server_buffer_integration->createServerBufferFromImage(image, QtWayland::ServerBuffer::RGBA32); + + m_server_buffers.append(buffer); + return buffer; +} + +void ShareBufferExtension::createServerBuffers() +{ + QImage image(100,100,QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(0x55,0x0,0x55,0x01)); + { + QPainter p(&image); + QPen pen = p.pen(); + pen.setWidthF(3); + pen.setColor(Qt::red); + p.setPen(pen); + p.drawLine(0,0,100,100); + pen.setColor(Qt::green); + p.setPen(pen); + p.drawLine(100,0,0,100); + pen.setColor(Qt::blue); + p.setPen(pen); + p.drawLine(25,15,75,15); + } + + addImage(image); + + QImage image2(":/images/Siberischer_tiger_de_edit02.jpg"); + addImage(image2); + + m_server_buffers_created = true; +} + + +void ShareBufferExtension::share_buffer_bind_resource(Resource *resource) +{ + if (!m_server_buffers_created) + createServerBuffers(); + + for (auto *buffer : std::as_const(m_server_buffers)) { + qDebug() << "sending" << buffer << "to client"; + struct ::wl_client *client = wl_resource_get_client(resource->handle); + struct ::wl_resource *buffer_resource = buffer->resourceForClient(client); + send_cross_buffer(resource->handle, buffer_resource); + } +} diff --git a/tests/manual/server-buffer/compositor/sharebufferextension.h b/tests/manual/server-buffer/compositor/sharebufferextension.h new file mode 100644 index 000000000..58809e3d4 --- /dev/null +++ b/tests/manual/server-buffer/compositor/sharebufferextension.h @@ -0,0 +1,53 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef SHAREBUFFEREXTENSION_H +#define SHAREBUFFEREXTENSION_H + +#include "wayland-util.h" + +#include <QtCore/QMap> + +#include <QtWaylandCompositor/QWaylandCompositorExtensionTemplate> +#include <QtWaylandCompositor/QWaylandQuickExtension> +#include <QtWaylandCompositor/QWaylandCompositor> + +#include <QtWaylandCompositor/private/qwaylandcompositor_p.h> +#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h> + +#include "qwayland-server-share-buffer.h" + +QT_BEGIN_NAMESPACE + +namespace QtWayland +{ + class ServerBufferIntegration; +}; + + +class ShareBufferExtension : public QWaylandCompositorExtensionTemplate<ShareBufferExtension> + , public QtWaylandServer::qt_share_buffer +{ + Q_OBJECT +public: + ShareBufferExtension(QWaylandCompositor *compositor = nullptr); + void initialize() override; + +protected slots: + QtWayland::ServerBuffer *addImage(const QImage &image); + +protected: + void share_buffer_bind_resource(Resource *resource) override; + +private: + void createServerBuffers(); + QList<QtWayland::ServerBuffer *> m_server_buffers; + QtWayland::ServerBufferIntegration *m_server_buffer_integration = nullptr; + bool m_server_buffers_created = false; +}; + +Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(ShareBufferExtension) + +QT_END_NAMESPACE + +#endif // SHAREBUFFEREXTENSION_H diff --git a/tests/manual/server-buffer/cpp-client/CMakeLists.txt b/tests/manual/server-buffer/cpp-client/CMakeLists.txt new file mode 100644 index 000000000..01c0df44f --- /dev/null +++ b/tests/manual/server-buffer/cpp-client/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(server-buffer-cpp-client) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/wayland/server-buffer/cpp-client") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL WaylandClient) + +qt_add_executable(server-buffer-cpp-client + main.cpp + sharebufferextension.cpp sharebufferextension.h +) + +qt6_generate_wayland_protocol_client_sources(server-buffer-cpp-client + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../share-buffer.xml +) + +set_target_properties(server-buffer-cpp-client PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(server-buffer-cpp-client PUBLIC + Qt::Core + Qt::Gui + Qt::GuiPrivate + Qt::OpenGL + Qt::WaylandClientPrivate +) + +install(TARGETS server-buffer-cpp-client + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/tests/manual/server-buffer/cpp-client/cpp-client.pro b/tests/manual/server-buffer/cpp-client/cpp-client.pro new file mode 100644 index 000000000..6ac551323 --- /dev/null +++ b/tests/manual/server-buffer/cpp-client/cpp-client.pro @@ -0,0 +1,15 @@ +QT += waylandclient-private gui-private opengl +CONFIG += wayland-scanner + +WAYLANDCLIENTSOURCES += ../share-buffer.xml + +SOURCES += main.cpp \ + sharebufferextension.cpp + +HEADERS += \ + sharebufferextension.h + +TARGET = server-buffer-cpp-client + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/server-buffer/cpp-client +INSTALLS += target diff --git a/tests/manual/server-buffer/cpp-client/main.cpp b/tests/manual/server-buffer/cpp-client/main.cpp new file mode 100644 index 000000000..b11921f67 --- /dev/null +++ b/tests/manual/server-buffer/cpp-client/main.cpp @@ -0,0 +1,88 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#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 "sharebufferextension.h" + +#include <QDebug> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QTimer> +#include <QMap> + +class TestWindow : public QOpenGLWindow +{ + Q_OBJECT + +public: + TestWindow() + { + m_extension = new ShareBufferExtension; + connect(m_extension, SIGNAL(bufferReceived(QtWaylandClient::QWaylandServerBuffer*)), this, SLOT(receiveBuffer(QtWaylandClient::QWaylandServerBuffer*))); + } + +public slots: + void receiveBuffer(QtWaylandClient::QWaylandServerBuffer *buffer) + { + m_buffers.append(buffer); + update(); + } + +protected: + + void initializeGL() override + { + m_blitter = new QOpenGLTextureBlitter; + m_blitter->create(); + } + + void paintGL() override { + glClearColor(.5, .45, .42, 1.); + glClear(GL_COLOR_BUFFER_BIT); + int x = 0; + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + for (auto buffer: m_buffers) { + m_blitter->bind(); + QPointF pos(x,0); + QSize s(buffer->size()); + QRectF targetRect(pos, s); + QOpenGLTexture *texture = buffer->toOpenGlTexture(); + 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: + + QOpenGLTextureBlitter *m_blitter = nullptr; + ShareBufferExtension *m_extension = nullptr; + QList<QtWaylandClient::QWaylandServerBuffer*> m_buffers; + +}; + +int main (int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + TestWindow window; + window.show(); + + return app.exec(); +} + +#include "main.moc" diff --git a/tests/manual/server-buffer/cpp-client/sharebufferextension.cpp b/tests/manual/server-buffer/cpp-client/sharebufferextension.cpp new file mode 100644 index 000000000..299684a89 --- /dev/null +++ b/tests/manual/server-buffer/cpp-client/sharebufferextension.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "sharebufferextension.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 + +ShareBufferExtension::ShareBufferExtension() + : 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 ShareBufferExtension::share_buffer_cross_buffer(struct ::qt_server_buffer *buffer) +{ + QtWaylandClient::QWaylandServerBuffer *serverBuffer = m_server_buffer_integration->serverBuffer(buffer); + emit bufferReceived(serverBuffer); +} + + +QT_END_NAMESPACE diff --git a/tests/manual/server-buffer/cpp-client/sharebufferextension.h b/tests/manual/server-buffer/cpp-client/sharebufferextension.h new file mode 100644 index 000000000..6fc429194 --- /dev/null +++ b/tests/manual/server-buffer/cpp-client/sharebufferextension.h @@ -0,0 +1,36 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef SHAREBUFFEREXTENSION_H +#define SHAREBUFFEREXTENSION_H + +#include <qpa/qwindowsysteminterface.h> +#include <QtWaylandClient/private/qwayland-wayland.h> +#include <QtWaylandClient/qwaylandclientextension.h> +#include "qwayland-share-buffer.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + class QWaylandServerBuffer; + class QWaylandServerBufferIntegration; +}; + +class ShareBufferExtension : public QWaylandClientExtensionTemplate<ShareBufferExtension> + , public QtWayland::qt_share_buffer +{ + Q_OBJECT +public: + ShareBufferExtension(); + +signals: + void bufferReceived(QtWaylandClient::QWaylandServerBuffer *buffer); + +private: + void share_buffer_cross_buffer(struct ::qt_server_buffer *buffer) override; + QtWaylandClient::QWaylandServerBufferIntegration *m_server_buffer_integration = nullptr; +}; + +QT_END_NAMESPACE + +#endif // SHAREBUFFEREXTENSION_H diff --git a/tests/manual/server-buffer/server-buffer.pro b/tests/manual/server-buffer/server-buffer.pro new file mode 100644 index 000000000..0c737ea8c --- /dev/null +++ b/tests/manual/server-buffer/server-buffer.pro @@ -0,0 +1,6 @@ +TEMPLATE=subdirs + +SUBDIRS += cpp-client compositor + +EXAMPLE_FILES += \ + share-buffer.xml diff --git a/tests/manual/server-buffer/share-buffer.xml b/tests/manual/server-buffer/share-buffer.xml new file mode 100644 index 000000000..2a4ae6748 --- /dev/null +++ b/tests/manual/server-buffer/share-buffer.xml @@ -0,0 +1,13 @@ +<protocol name="share_buffer"> + + <copyright> + Copyright (C) 2015 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + </copyright> + + <interface name="qt_share_buffer" version="1"> + <event name="cross_buffer"> + <arg name="buffer" type="object" interface="qt_server_buffer"/> + </event> + </interface> +</protocol> diff --git a/tests/manual/subsurface/CMakeLists.txt b/tests/manual/subsurface/CMakeLists.txt new file mode 100644 index 000000000..0c1b50e0c --- /dev/null +++ b/tests/manual/subsurface/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from subsurface.pro. + +##################################################################### +## subsurface Binary: +##################################################################### + +qt_internal_add_manual_test(subsurface + GUI + SOURCES + main.cpp + shmwindow.cpp shmwindow.h + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + Qt::Quick + Qt::WaylandClient +) + +# Resources: +set(qml_resource_files + "child.qml" + "main.qml" +) + +qt_internal_add_resource(subsurface "qml" + PREFIX + "/" + FILES + ${qml_resource_files} +) + + +#### Keys ignored in scope 1:.:.:subsurface.pro:<TRUE>: +# TEMPLATE = "app" diff --git a/tests/manual/subsurface/child.qml b/tests/manual/subsurface/child.qml index f1bdd66ef..82d2e0757 100644 --- a/tests/manual/subsurface/child.qml +++ b/tests/manual/subsurface/child.qml @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2015 LG Electronics Inc, author: <mikko.levonmaa@lge.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2015 LG Electronics Inc, author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.4 import QtQuick.Window 2.2 diff --git a/tests/manual/subsurface/main.cpp b/tests/manual/subsurface/main.cpp index 375e5d2b9..c353496c0 100644 --- a/tests/manual/subsurface/main.cpp +++ b/tests/manual/subsurface/main.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2015 LG Electronics Inc, author: <mikko.levonmaa@lge.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2015 LG Electronics Inc, author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QGuiApplication> #include <QQmlEngine> @@ -54,8 +7,6 @@ #include <QQmlContext> #include <QQuickView> -#include <QtPlatformHeaders/qwaylandwindowfunctions.h> - #include "shmwindow.h" class Filter : public QObject @@ -84,12 +35,6 @@ public: void toggleSync(QWindow *w) { - sync = !QWaylandWindowFunctions::isSync(w); - if (QWaylandWindowFunctions::isSync(w)) - QWaylandWindowFunctions::setDeSync(w); - else - QWaylandWindowFunctions::setSync(w); - emit syncChanged(); } bool getSync() const diff --git a/tests/manual/subsurface/main.qml b/tests/manual/subsurface/main.qml index 8c27ac78d..e6f7eeb79 100644 --- a/tests/manual/subsurface/main.qml +++ b/tests/manual/subsurface/main.qml @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2015 LG Electronics Inc, author: <mikko.levonmaa@lge.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2015 LG Electronics Inc, author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.4 import QtQuick.Window 2.2 diff --git a/tests/manual/subsurface/shmwindow.cpp b/tests/manual/subsurface/shmwindow.cpp index 323582101..c6b59d132 100644 --- a/tests/manual/subsurface/shmwindow.cpp +++ b/tests/manual/subsurface/shmwindow.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2015 LG Electronics Ltd, author: <mikko.levonmaa@lge.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2015 LG Electronics Ltd, author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "shmwindow.h" diff --git a/tests/manual/subsurface/shmwindow.h b/tests/manual/subsurface/shmwindow.h index 10041eea9..55a0336e6 100644 --- a/tests/manual/subsurface/shmwindow.h +++ b/tests/manual/subsurface/shmwindow.h @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2015 LG Electronics Ltd, author: <mikko.levonmaa@lge.com> -** Contact: https://www.qt.io/licensing/ -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2015 LG Electronics Ltd, author: <mikko.levonmaa@lge.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef SHMWINDOW_H #define SHMWINDOW_H diff --git a/tests/manual/texture-sharing-2/.gitignore b/tests/manual/texture-sharing-2/.gitignore new file mode 100644 index 000000000..c684448d3 --- /dev/null +++ b/tests/manual/texture-sharing-2/.gitignore @@ -0,0 +1,2 @@ +custom-compositor/custom-compositor +qml-client/qml-client diff --git a/tests/manual/texture-sharing-2/CMakeLists.txt b/tests/manual/texture-sharing-2/CMakeLists.txt new file mode 100644 index 000000000..9a212ea3e --- /dev/null +++ b/tests/manual/texture-sharing-2/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from texture-sharing.pro. + +add_subdirectory(qml-client) +add_subdirectory(custom-compositor) diff --git a/tests/manual/texture-sharing-2/README b/tests/manual/texture-sharing-2/README new file mode 100644 index 000000000..27ea76745 --- /dev/null +++ b/tests/manual/texture-sharing-2/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/tests/manual/texture-sharing-2/custom-compositor/CMakeLists.txt b/tests/manual/texture-sharing-2/custom-compositor/CMakeLists.txt new file mode 100644 index 000000000..1a6c1494a --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(texture-sharing-custom-compositor + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Qml + Qt::WaylandCompositorPrivate +) + + +# Resources: +set(compositor_resource_files + "images/background.png" + "images/car.ktx" + "images/qt4.astc" + "images/qt_logo.png" + "qml/main.qml" +) + +qt_internal_add_resource(texture-sharing-custom-compositor "compositor" + PREFIX + "/" + FILES + ${compositor_resource_files} +) diff --git a/tests/manual/texture-sharing-2/custom-compositor/compositor.qrc b/tests/manual/texture-sharing-2/custom-compositor/compositor.qrc new file mode 100644 index 000000000..86a8567f7 --- /dev/null +++ b/tests/manual/texture-sharing-2/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/tests/manual/texture-sharing-2/custom-compositor/custom-compositor.pro b/tests/manual/texture-sharing-2/custom-compositor/custom-compositor.pro new file mode 100644 index 000000000..098034f3b --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/custom-compositor.pro @@ -0,0 +1,17 @@ +QT += core gui qml + +QT += waylandcompositor-private + +SOURCES += \ + main.cpp + +OTHER_FILES = \ + qml/main.qml \ + images/background.jpg + +RESOURCES += compositor.qrc + +TARGET = texture-sharing-custom-compositor + +target.path = $$[QT_INSTALL_EXAMPLES]/wayland/texture-sharing/custom-compositor +INSTALLS += target diff --git a/tests/manual/texture-sharing-2/custom-compositor/images/background.png b/tests/manual/texture-sharing-2/custom-compositor/images/background.png Binary files differnew file mode 100644 index 000000000..845830c59 --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/images/background.png diff --git a/tests/manual/texture-sharing-2/custom-compositor/images/car.ktx b/tests/manual/texture-sharing-2/custom-compositor/images/car.ktx Binary files differnew file mode 100644 index 000000000..2aefdd306 --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/images/car.ktx diff --git a/tests/manual/texture-sharing-2/custom-compositor/images/qt4.astc b/tests/manual/texture-sharing-2/custom-compositor/images/qt4.astc Binary files differnew file mode 100644 index 000000000..7f7a3f473 --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/images/qt4.astc diff --git a/tests/manual/texture-sharing-2/custom-compositor/images/qt_logo.png b/tests/manual/texture-sharing-2/custom-compositor/images/qt_logo.png Binary files differnew file mode 100644 index 000000000..5e2b355ea --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/images/qt_logo.png diff --git a/tests/manual/texture-sharing-2/custom-compositor/main.cpp b/tests/manual/texture-sharing-2/custom-compositor/main.cpp new file mode 100644 index 000000000..0c229413b --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/main.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#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" + +#ifndef GL_RGBA8 +#define GL_RGBA8 0x8058 +#endif + +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>("io.qt.tests.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/tests/manual/texture-sharing-2/custom-compositor/qml/main.qml b/tests/manual/texture-sharing-2/custom-compositor/qml/main.qml new file mode 100644 index 000000000..dd1f60f4e --- /dev/null +++ b/tests/manual/texture-sharing-2/custom-compositor/qml/main.qml @@ -0,0 +1,72 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtWayland.Compositor +import QtWayland.Compositor.XdgShell +import QtWayland.Compositor.WlShell + +import io.qt.tests.customsharingextension + +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 { + shellSurface: modelData + onSurfaceDestroyed: shellSurfaces.remove(index) + } + } + } + } + WlShell { + onWlShellSurfaceCreated: + shellSurfaces.append({shellSurface: shellSurface}); + } + XdgShell { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } + ListModel { id: shellSurfaces } + + CustomSharingExtension { + imageSearchPath: ":/images;." + } +} diff --git a/tests/manual/texture-sharing-2/minimal-compositor.qml b/tests/manual/texture-sharing-2/minimal-compositor.qml new file mode 100644 index 000000000..d12216156 --- /dev/null +++ b/tests/manual/texture-sharing-2/minimal-compositor.qml @@ -0,0 +1,43 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window +import QtWayland.Compositor +import QtWayland.Compositor.XdgShell +import QtWayland.Compositor.WlShell + +// importing the texture sharing extension: +import QtWayland.Compositor.TextureSharingExtension + +WaylandCompositor { + WaylandOutput { + sizeFollowsWindow: true + window: Window { + width: 1024 + height: 768 + visible: true + Repeater { + model: shellSurfaces + ShellSurfaceItem { + shellSurface: modelData + onSurfaceDestroyed: shellSurfaces.remove(index) + } + } + } + } + WlShell { + onWlShellSurfaceCreated: + shellSurfaces.append({shellSurface: shellSurface}); + } + XdgShell { + onToplevelCreated: + shellSurfaces.append({shellSurface: xdgSurface}); + } + ListModel { id: shellSurfaces } + + // instantiating the texture sharing extension: + TextureSharingExtension { + imageSearchPath: ".;/tmp;/usr/share/pixmaps" + } +} diff --git a/tests/manual/texture-sharing-2/qml-client/CMakeLists.txt b/tests/manual/texture-sharing-2/qml-client/CMakeLists.txt new file mode 100644 index 000000000..ce6bc118d --- /dev/null +++ b/tests/manual/texture-sharing-2/qml-client/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_manual_test(qml-client + GUI + SOURCES + main.cpp + LIBRARIES + Qt::Quick +) + +# Resources: +set(qml-client_resource_files + "main.qml" +) + +qt_internal_add_resource(qml-client "qml-client" + PREFIX + "/" + FILES + ${qml-client_resource_files} +) diff --git a/tests/manual/texture-sharing-2/qml-client/main.cpp b/tests/manual/texture-sharing-2/qml-client/main.cpp new file mode 100644 index 000000000..8af386939 --- /dev/null +++ b/tests/manual/texture-sharing-2/qml-client/main.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#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/tests/manual/texture-sharing-2/qml-client/main.qml b/tests/manual/texture-sharing-2/qml-client/main.qml new file mode 100644 index 000000000..819a93bcf --- /dev/null +++ b/tests/manual/texture-sharing-2/qml-client/main.qml @@ -0,0 +1,201 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Window + +import QtWayland.Client.TextureSharing + +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/tests/manual/texture-sharing-2/qml-client/qml-client.pro b/tests/manual/texture-sharing-2/qml-client/qml-client.pro new file mode 100644 index 000000000..67d5c7071 --- /dev/null +++ b/tests/manual/texture-sharing-2/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/tests/manual/texture-sharing-2/qml-client/qml-client.qrc b/tests/manual/texture-sharing-2/qml-client/qml-client.qrc new file mode 100644 index 000000000..5f6483ac3 --- /dev/null +++ b/tests/manual/texture-sharing-2/qml-client/qml-client.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>main.qml</file> + </qresource> +</RCC> diff --git a/tests/manual/texture-sharing-2/texture-sharing.pro b/tests/manual/texture-sharing-2/texture-sharing.pro new file mode 100644 index 000000000..3f7792828 --- /dev/null +++ b/tests/manual/texture-sharing-2/texture-sharing.pro @@ -0,0 +1,5 @@ +TEMPLATE=subdirs + +SUBDIRS += \ + qml-client \ + custom-compositor diff --git a/tests/manual/texture-sharing/cpp-client/CMakeLists.txt b/tests/manual/texture-sharing/cpp-client/CMakeLists.txt new file mode 100644 index 000000000..1059e6e5e --- /dev/null +++ b/tests/manual/texture-sharing/cpp-client/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from cpp-client.pro. + +##################################################################### +## cpp-client Binary: +##################################################################### + +qt_internal_add_manual_test(cpp-client + GUI + SOURCES + ../../../../src/imports/texture-sharing/texturesharingextension.cpp + ../../../../src/imports/texture-sharing/texturesharingextension_p.h + main.cpp + INCLUDE_DIRECTORIES + ../../../../src/imports/texture-sharing + LIBRARIES + Qt::Gui + Qt::GuiPrivate + Qt::OpenGL + Qt::WaylandClientPrivate +) + +qt6_generate_wayland_protocol_client_sources(cpp-client + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/extensions/qt-texture-sharing-unstable-v1.xml +) + +#### Keys ignored in scope 1:.:.:cpp-client.pro:<TRUE>: +# INSTALLS = "target" +# target.path = "$$[QT_INSTALL_EXAMPLES]/wayland/texture-sharing/cpp-client" 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..99a100e07 --- /dev/null +++ b/tests/manual/texture-sharing/cpp-client/cpp-client.pro @@ -0,0 +1,15 @@ +QT += waylandclient-private gui-private opengl +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..f992ea1b6 --- /dev/null +++ b/tests/manual/texture-sharing/cpp-client/main.cpp @@ -0,0 +1,182 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#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: std::as_const(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" diff --git a/tests/manual/wip-cpp-compositor/CMakeLists.txt b/tests/manual/wip-cpp-compositor/CMakeLists.txt new file mode 100644 index 000000000..4d8a166ed --- /dev/null +++ b/tests/manual/wip-cpp-compositor/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from wip-cpp-compositor.pro. + +##################################################################### +## wip-cpp-compositor Binary: +##################################################################### + +qt_internal_add_manual_test(wip-cpp-compositor + GUI + SOURCES + compositor.cpp compositor.h + main.cpp + window.cpp window.h + LIBRARIES + Qt::Gui + Qt::WaylandCompositor +) + +#### Keys ignored in scope 1:.:.:wip-cpp-compositor.pro:<TRUE>: +# INSTALLS = "target" +# target.path = "$$[QT_INSTALL_EXAMPLES]/wayland/reference-cpp" diff --git a/tests/manual/wip-cpp-compositor/compositor.cpp b/tests/manual/wip-cpp-compositor/compositor.cpp index d65c5f0c3..2f7025115 100644 --- a/tests/manual/wip-cpp-compositor/compositor.cpp +++ b/tests/manual/wip-cpp-compositor/compositor.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "compositor.h" #include "window.h" @@ -80,7 +33,7 @@ QPoint View::mapToLocal(const QPoint &globalPosition) const void View::updateAnchoredPosition() { QPoint offset; - QSize size = surface()->size(); + QSize size = surface()->bufferSize(); QSize delta = size - m_lastSize; if (m_anchorEdges & Qt::RightEdge) offset.setX(-delta.width()); @@ -109,7 +62,7 @@ ToplevelView::ToplevelView(QWaylandXdgToplevel *toplevel) setAnchorEdges(opposite); emit startResize(); }); - QVector<QWaylandXdgToplevel::State> states{QWaylandXdgToplevel::ActivatedState}; + QList<QWaylandXdgToplevel::State> states{QWaylandXdgToplevel::ActivatedState}; toplevel->sendConfigure(QSize(0, 0), states); } diff --git a/tests/manual/wip-cpp-compositor/compositor.h b/tests/manual/wip-cpp-compositor/compositor.h index 5c2d6a2f5..b2810d0da 100644 --- a/tests/manual/wip-cpp-compositor/compositor.h +++ b/tests/manual/wip-cpp-compositor/compositor.h @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef COMPOSITOR_H #define COMPOSITOR_H @@ -71,8 +24,8 @@ class View : public QWaylandView public: explicit View() = default; QOpenGLTexture *getTexture(); - QSize size() const { return surface() ? surface()->size() : QSize(); } - QRect globalGeometry() const { return {globalPosition(), surface()->size()}; } + QSize bufferSize() const { return surface() ? surface()->bufferSize() : QSize(); } + QRect globalGeometry() const { return {globalPosition(), surface()->bufferSize()}; } QPoint globalPosition() const { return m_globalPosition; } void setGlobalPosition(const QPoint &position); QPoint mapToLocal(const QPoint &globalPosition) const; diff --git a/tests/manual/wip-cpp-compositor/main.cpp b/tests/manual/wip-cpp-compositor/main.cpp index 8ca5a6f33..e8a34a14f 100644 --- a/tests/manual/wip-cpp-compositor/main.cpp +++ b/tests/manual/wip-cpp-compositor/main.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QGuiApplication> #include "window.h" diff --git a/tests/manual/wip-cpp-compositor/window.cpp b/tests/manual/wip-cpp-compositor/window.cpp index b5b2581e0..413e56572 100644 --- a/tests/manual/wip-cpp-compositor/window.cpp +++ b/tests/manual/wip-cpp-compositor/window.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "window.h" #include "compositor.h" @@ -116,23 +69,28 @@ void Window::paintGL() void Window::mousePressEvent(QMouseEvent *event) { - m_compositor->handleMousePress(event->localPos().toPoint(), event->button()); + m_compositor->handleMousePress(event->position().toPoint(), event->button()); } void Window::mouseReleaseEvent(QMouseEvent *event) { - m_compositor->handleMouseRelease(event->localPos().toPoint(), event->button(), event->buttons()); + m_compositor->handleMouseRelease(event->position().toPoint(), event->button(), event->buttons()); } void Window::mouseMoveEvent(QMouseEvent *event) { - m_compositor->handleMouseMove(event->localPos().toPoint()); + m_compositor->handleMouseMove(event->position().toPoint()); } +#if QT_CONFIG(wheelevent) void Window::wheelEvent(QWheelEvent *event) { - m_compositor->handleMouseWheel(event->orientation(), event->delta()); + if (event->angleDelta().x() != 0) + m_compositor->handleMouseWheel(Qt::Horizontal, event->angleDelta().x()); + if (event->angleDelta().y() != 0) + m_compositor->handleMouseWheel(Qt::Vertical, event->angleDelta().y()); } +#endif void Window::keyPressEvent(QKeyEvent *event) { diff --git a/tests/manual/wip-cpp-compositor/window.h b/tests/manual/wip-cpp-compositor/window.h index bc71207ee..58ed1d5a0 100644 --- a/tests/manual/wip-cpp-compositor/window.h +++ b/tests/manual/wip-cpp-compositor/window.h @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef WINDOW_H #define WINDOW_H @@ -71,7 +24,9 @@ protected: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; +#if QT_CONFIG(wheelevent) void wheelEvent(QWheelEvent *event) override; +#endif void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; diff --git a/tests/tests.pro b/tests/tests.pro deleted file mode 100644 index 85e4f3a53..000000000 --- a/tests/tests.pro +++ /dev/null @@ -1,2 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS += auto |