summaryrefslogtreecommitdiffstats
path: root/tests/auto/client
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/client')
-rw-r--r--tests/auto/client/client.pro2
-rw-r--r--tests/auto/client/client/tst_client.cpp8
-rw-r--r--tests/auto/client/datadevicev1/tst_datadevicev1.cpp38
-rw-r--r--tests/auto/client/inputcontext/inputcontext.pro2
-rw-r--r--tests/auto/client/output/tst_output.cpp3
-rw-r--r--tests/auto/client/primaryselectionv1/primaryselectionv1.pro7
-rw-r--r--tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp506
-rw-r--r--tests/auto/client/seatv4/BLACKLIST2
-rw-r--r--tests/auto/client/seatv4/tst_seatv4.cpp57
-rw-r--r--tests/auto/client/seatv5/seatv5.pro4
-rw-r--r--tests/auto/client/seatv5/tst_seatv5.cpp590
-rw-r--r--tests/auto/client/shared/corecompositor.h17
-rw-r--r--tests/auto/client/shared/coreprotocol.cpp115
-rw-r--r--tests/auto/client/shared/coreprotocol.h26
-rw-r--r--tests/auto/client/shared/datadevice.h1
-rw-r--r--tests/auto/client/shared/mockcompositor.cpp2
-rw-r--r--tests/auto/client/shared/mockcompositor.h9
-rw-r--r--tests/auto/client/shared_old/mockcompositor.cpp4
-rw-r--r--tests/auto/client/shared_old/mocksurface.cpp3
-rw-r--r--tests/auto/client/surface/tst_surface.cpp6
-rw-r--r--tests/auto/client/xdgshell/tst_xdgshell.cpp77
-rw-r--r--tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp4
22 files changed, 1427 insertions, 56 deletions
diff --git a/tests/auto/client/client.pro b/tests/auto/client/client.pro
index 06c1cb877..4b1eb2458 100644
--- a/tests/auto/client/client.pro
+++ b/tests/auto/client/client.pro
@@ -6,7 +6,9 @@ SUBDIRS += \
fullscreenshellv1 \
iviapplication \
output \
+ primaryselectionv1 \
seatv4 \
+ seatv5 \
surface \
wl_connect \
xdgdecorationv1 \
diff --git a/tests/auto/client/client/tst_client.cpp b/tests/auto/client/client/tst_client.cpp
index e7729ce8b..499a93a1d 100644
--- a/tests/auto/client/client/tst_client.cpp
+++ b/tests/auto/client/client/tst_client.cpp
@@ -36,7 +36,9 @@
#include <QPixmap>
#include <QDrag>
#include <QWindow>
+#if QT_CONFIG(opengl)
#include <QOpenGLWindow>
+#endif
#include <QtTest/QtTest>
#include <QtWaylandClient/private/qwaylandintegration_p.h>
@@ -107,6 +109,7 @@ public:
QPoint mousePressPos;
};
+#if QT_CONFIG(opengl)
class TestGlWindow : public QOpenGLWindow
{
Q_OBJECT
@@ -136,6 +139,7 @@ void TestGlWindow::paintGL()
glClear(GL_COLOR_BUFFER_BIT);
++paintGLCalled;
}
+#endif // QT_CONFIG(opengl)
class tst_WaylandClient : public QObject
{
@@ -176,7 +180,9 @@ private slots:
void dontCrashOnMultipleCommits();
void hiddenTransientParent();
void hiddenPopupParent();
+#if QT_CONFIG(opengl)
void glWindow();
+#endif // QT_CONFIG(opengl)
void longWindowTitle();
void longWindowTitleWithUtf16Characters();
@@ -459,6 +465,7 @@ void tst_WaylandClient::hiddenPopupParent()
QTRY_VERIFY(compositor->surface());
}
+#if QT_CONFIG(opengl)
void tst_WaylandClient::glWindow()
{
QSKIP("Skipping GL tests, as not supported by all CI systems: See https://bugreports.qt.io/browse/QTBUG-65802");
@@ -484,6 +491,7 @@ void tst_WaylandClient::glWindow()
testWindow->setVisible(false);
QTRY_VERIFY(!compositor->surface());
}
+#endif // QT_CONFIG(opengl)
void tst_WaylandClient::longWindowTitle()
{
diff --git a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp
index 7368829d1..1568b3b96 100644
--- a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp
+++ b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp
@@ -59,6 +59,7 @@ private slots:
void pasteUtf8();
void destroysPreviousSelection();
void destroysSelectionWithSurface();
+ void destroysSelectionOnLeave();
void dragWithoutFocus();
};
@@ -66,7 +67,7 @@ void tst_datadevicev1::initTestCase()
{
QCOMPOSITOR_TRY_VERIFY(pointer());
QCOMPOSITOR_TRY_VERIFY(!pointer()->resourceMap().empty());
- QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 4);
+ QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 5);
QCOMPOSITOR_TRY_VERIFY(keyboard());
@@ -104,8 +105,11 @@ void tst_datadevicev1::pasteAscii()
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");
}
@@ -139,8 +143,11 @@ void tst_datadevicev1::pasteUtf8()
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: 😂");
}
@@ -209,6 +216,35 @@ void tst_datadevicev1::destroysSelectionWithSurface()
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.count(), 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()
diff --git a/tests/auto/client/inputcontext/inputcontext.pro b/tests/auto/client/inputcontext/inputcontext.pro
index 4419b3e77..1971d455e 100644
--- a/tests/auto/client/inputcontext/inputcontext.pro
+++ b/tests/auto/client/inputcontext/inputcontext.pro
@@ -1,6 +1,4 @@
include (../shared/shared.pri)
-QT += waylandcompositor
-
TARGET = tst_inputcontext
SOURCES += tst_inputcontext.cpp
diff --git a/tests/auto/client/output/tst_output.cpp b/tests/auto/client/output/tst_output.cpp
index 2d2c8efd6..29c773cf6 100644
--- a/tests/auto/client/output/tst_output.cpp
+++ b/tests/auto/client/output/tst_output.cpp
@@ -196,8 +196,11 @@ void tst_output::removePrimaryScreen()
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
diff --git a/tests/auto/client/primaryselectionv1/primaryselectionv1.pro b/tests/auto/client/primaryselectionv1/primaryselectionv1.pro
new file mode 100644
index 000000000..9d00562df
--- /dev/null
+++ b/tests/auto/client/primaryselectionv1/primaryselectionv1.pro
@@ -0,0 +1,7 @@
+include (../shared/shared.pri)
+
+WAYLANDSERVERSOURCES += \
+ $$PWD/../../../../src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml
+
+TARGET = tst_primaryselectionv1
+SOURCES += tst_primaryselectionv1.cpp
diff --git a/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp
new file mode 100644
index 000000000..ee9fa110e
--- /dev/null
+++ b/tests/auto/client/primaryselectionv1/tst_primaryselectionv1.cpp
@@ -0,0 +1,506 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mockcompositor.h"
+
+#include <qwayland-server-wp-primary-selection-unstable-v1.h>
+
+#include <QtGui/QRasterWindow>
+#include <QtGui/QOpenGLWindow>
+#include <QtGui/QClipboard>
+#include <QtCore/private/qcore_unix_p.h>
+
+#include <fcntl.h>
+
+using namespace MockCompositor;
+
+constexpr int primarySelectionVersion = 1; // protocol VERSION, not the name suffix (_v1)
+
+class PrimarySelectionDeviceV1;
+class PrimarySelectionDeviceManagerV1;
+
+class PrimarySelectionOfferV1 : public QObject, public QtWaylandServer::zwp_primary_selection_offer_v1
+{
+ Q_OBJECT
+public:
+ explicit PrimarySelectionOfferV1(PrimarySelectionDeviceV1 *device, wl_client *client, int version)
+ : zwp_primary_selection_offer_v1(client, 0, version)
+ , m_device(device)
+ {}
+ void send_offer() = delete;
+ void sendOffer(const QString &offer)
+ {
+ zwp_primary_selection_offer_v1::send_offer(offer);
+ m_mimeTypes << offer;
+ }
+
+ PrimarySelectionDeviceV1 *m_device = nullptr;
+ QStringList m_mimeTypes;
+
+signals:
+ void receive(QString mimeType, int fd);
+
+protected:
+ void zwp_primary_selection_offer_v1_destroy_resource(Resource *resource) override
+ {
+ Q_UNUSED(resource);
+ delete this;
+ }
+
+ void zwp_primary_selection_offer_v1_receive(Resource *resource, const QString &mime_type, int32_t fd) override
+ {
+ Q_UNUSED(resource);
+ QTRY_VERIFY(m_mimeTypes.contains(mime_type));
+ emit receive(mime_type, fd);
+ }
+
+ void zwp_primary_selection_offer_v1_destroy(Resource *resource) override;
+};
+
+class PrimarySelectionSourceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_source_v1
+{
+ Q_OBJECT
+public:
+ explicit PrimarySelectionSourceV1(wl_client *client, int id, int version)
+ : zwp_primary_selection_source_v1(client, id, version)
+ {
+ }
+ QStringList m_offers;
+protected:
+ void zwp_primary_selection_source_v1_destroy_resource(Resource *resource) override
+ {
+ Q_UNUSED(resource);
+ delete this;
+ }
+ void zwp_primary_selection_source_v1_offer(Resource *resource, const QString &mime_type) override
+ {
+ Q_UNUSED(resource);
+ m_offers << mime_type;
+ }
+ void zwp_primary_selection_source_v1_destroy(Resource *resource) override
+ {
+ wl_resource_destroy(resource->handle);
+ }
+};
+
+class PrimarySelectionDeviceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_device_v1
+{
+ Q_OBJECT
+public:
+ explicit PrimarySelectionDeviceV1(PrimarySelectionDeviceManagerV1 *manager, Seat *seat)
+ : m_manager(manager)
+ , m_seat(seat)
+ {}
+
+ void send_data_offer(::wl_resource *resource) = delete;
+
+ PrimarySelectionOfferV1 *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {});
+
+ PrimarySelectionOfferV1 *sendDataOffer(const QStringList &mimeTypes = {}) // creates a new offer for the focused surface and sends it
+ {
+ Q_ASSERT(m_seat->m_capabilities & Seat::capability_keyboard);
+ Q_ASSERT(m_seat->m_keyboard->m_enteredSurface);
+ auto *client = m_seat->m_keyboard->m_enteredSurface->resource()->client();
+ return sendDataOffer(client, mimeTypes);
+ }
+
+ void send_selection(::wl_resource *resource) = delete;
+ void sendSelection(PrimarySelectionOfferV1 *offer)
+ {
+ auto *client = offer->resource()->client();
+ for (auto *resource : resourceMap().values(client))
+ zwp_primary_selection_device_v1::send_selection(resource->handle, offer->resource()->handle);
+ m_sentSelectionOffers << offer;
+ }
+
+ PrimarySelectionDeviceManagerV1 *m_manager = nullptr;
+ Seat *m_seat = nullptr;
+ QVector<PrimarySelectionOfferV1 *> m_sentSelectionOffers;
+ PrimarySelectionSourceV1 *m_selectionSource = nullptr;
+ uint m_serial = 0;
+
+protected:
+ void zwp_primary_selection_device_v1_set_selection(Resource *resource, ::wl_resource *source, uint32_t serial) override
+ {
+ Q_UNUSED(resource);
+ m_selectionSource = fromResource<PrimarySelectionSourceV1>(source);
+ m_serial = serial;
+ }
+ void zwp_primary_selection_device_v1_destroy(Resource *resource) override
+ {
+ wl_resource_destroy(resource->handle);
+ }
+ void zwp_primary_selection_device_v1_destroy_resource(Resource *resource) override
+ {
+ Q_UNUSED(resource);
+ delete this;
+ }
+};
+
+class PrimarySelectionDeviceManagerV1 : public Global, public QtWaylandServer::zwp_primary_selection_device_manager_v1
+{
+ Q_OBJECT
+public:
+ explicit PrimarySelectionDeviceManagerV1(CoreCompositor *compositor, int version = 1)
+ : QtWaylandServer::zwp_primary_selection_device_manager_v1(compositor->m_display, version)
+ , m_version(version)
+ {}
+ bool isClean() override
+ {
+ for (auto *device : qAsConst(m_devices)) {
+ // The client should not leak selection offers, i.e. if this fails, there is a missing
+ // zwp_primary_selection_offer_v1.destroy request
+ if (!device->m_sentSelectionOffers.empty())
+ return false;
+ }
+ return true;
+ }
+
+ PrimarySelectionDeviceV1 *deviceFor(Seat *seat)
+ {
+ Q_ASSERT(seat);
+ if (auto *device = m_devices.value(seat, nullptr))
+ return device;
+
+ auto *device = new PrimarySelectionDeviceV1(this, seat);
+ m_devices[seat] = device;
+ return device;
+ }
+
+ int m_version = 1; // TODO: Remove on libwayland upgrade
+ QMap<Seat *, PrimarySelectionDeviceV1 *> m_devices;
+ QVector<PrimarySelectionSourceV1 *> m_sources;
+protected:
+ void zwp_primary_selection_device_manager_v1_destroy(Resource *resource) override
+ {
+ // The protocol doesn't say whether managed objects should be destroyed as well,
+ // so leave them alone, they'll be cleaned up in the destructor anyway
+ wl_resource_destroy(resource->handle);
+ }
+
+ void zwp_primary_selection_device_manager_v1_create_source(Resource *resource, uint32_t id) override
+ {
+ int version = m_version;
+ m_sources << new PrimarySelectionSourceV1(resource->client(), id, version);
+ }
+ void zwp_primary_selection_device_manager_v1_get_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override
+ {
+ auto *seat = fromResource<Seat>(seatResource);
+ QVERIFY(seat);
+ auto *device = deviceFor(seat);
+ device->add(resource->client(), id, resource->version());
+ }
+};
+
+PrimarySelectionOfferV1 *PrimarySelectionDeviceV1::sendDataOffer(wl_client *client, const QStringList &mimeTypes)
+{
+ Q_ASSERT(client);
+ auto *offer = new PrimarySelectionOfferV1(this, client, m_manager->m_version);
+ for (auto *resource : resourceMap().values(client))
+ zwp_primary_selection_device_v1::send_data_offer(resource->handle, offer->resource()->handle);
+ for (const auto &mimeType : mimeTypes)
+ offer->sendOffer(mimeType);
+ return offer;
+}
+
+void PrimarySelectionOfferV1::zwp_primary_selection_offer_v1_destroy(QtWaylandServer::zwp_primary_selection_offer_v1::Resource *resource)
+{
+ bool removed = m_device->m_sentSelectionOffers.removeOne(this);
+ QVERIFY(removed);
+ wl_resource_destroy(resource->handle);
+}
+
+class PrimarySelectionCompositor : public DefaultCompositor {
+public:
+ explicit PrimarySelectionCompositor()
+ {
+ exec([this] {
+ m_config.autoConfigure = true;
+ add<PrimarySelectionDeviceManagerV1>(primarySelectionVersion);
+ });
+ }
+ PrimarySelectionDeviceV1 *primarySelectionDevice(int i = 0) {
+ return get<PrimarySelectionDeviceManagerV1>()->deviceFor(get<Seat>(i));
+ }
+};
+
+class tst_primaryselectionv1 : public QObject, private PrimarySelectionCompositor
+{
+ Q_OBJECT
+private slots:
+ void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
+ void initTestCase();
+ void bindsToManager();
+ void createsPrimaryDevice();
+ void createsPrimaryDeviceForNewSeats();
+ void pasteAscii();
+ void pasteUtf8();
+ void destroysPreviousSelection();
+ void destroysSelectionOnLeave();
+ void copy();
+};
+
+void tst_primaryselectionv1::initTestCase()
+{
+ QCOMPOSITOR_TRY_VERIFY(pointer());
+ QCOMPOSITOR_TRY_VERIFY(!pointer()->resourceMap().empty());
+ QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 5);
+
+ QCOMPOSITOR_TRY_VERIFY(keyboard());
+}
+
+void tst_primaryselectionv1::bindsToManager()
+{
+ QCOMPOSITOR_TRY_COMPARE(get<PrimarySelectionDeviceManagerV1>()->resourceMap().size(), 1);
+ QCOMPOSITOR_TRY_COMPARE(get<PrimarySelectionDeviceManagerV1>()->resourceMap().first()->version(), primarySelectionVersion);
+}
+
+void tst_primaryselectionv1::createsPrimaryDevice()
+{
+ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice());
+ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->resourceMap().contains(client()));
+ QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->resourceMap().value(client())->version(), primarySelectionVersion);
+ QTRY_VERIFY(QGuiApplication::clipboard()->supportsSelection());
+}
+
+void tst_primaryselectionv1::createsPrimaryDeviceForNewSeats()
+{
+ exec([=] { add<Seat>(); });
+ QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice(1));
+}
+
+void tst_primaryselectionv1::pasteAscii()
+{
+ class Window : public QRasterWindow {
+ public:
+ void mousePressEvent(QMouseEvent *event) override
+ {
+ Q_UNUSED(event);
+ auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
+ m_formats = mimeData->formats();
+ m_text = QGuiApplication::clipboard()->text(QClipboard::Selection);
+ }
+ QStringList m_formats;
+ QString m_text;
+ };
+
+ Window window;
+ window.resize(64, 64);
+ window.show();
+
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
+
+ auto *device = primarySelectionDevice();
+ auto *offer = device->sendDataOffer({"text/plain"});
+ connect(offer, &PrimarySelectionOfferV1::receive, [](QString mimeType, int fd) {
+ QFile file;
+ file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
+ QCOMPARE(mimeType, "text/plain");
+ file.write(QByteArray("normal ascii"));
+ file.close();
+ });
+ device->sendSelection(offer);
+
+ pointer()->sendEnter(surface, {32, 32});
+ pointer()->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, [](QString mimeType, int fd) {
+ QFile file;
+ file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
+ QCOMPARE(mimeType, "text/plain;charset=utf-8");
+ file.write(QByteArray("face with tears of joy: 😂"));
+ file.close();
+ });
+ device->sendSelection(offer);
+
+ pointer()->sendEnter(surface, {32, 32});
+ pointer()->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.count(), 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);
+ QVector<uint> mouseSerials;
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
+ pointer()->sendEnter(surface, {32, 32});
+ 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);
+ }
+ });
+ });
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(pastedBuf.size()); // this assumes we got everything in one read
+ auto pasted = QString::fromUtf8(pastedBuf);
+ QCOMPARE(pasted, "face with tears of joy: 😂");
+}
+
+QCOMPOSITOR_TEST_MAIN(tst_primaryselectionv1)
+#include "tst_primaryselectionv1.moc"
diff --git a/tests/auto/client/seatv4/BLACKLIST b/tests/auto/client/seatv4/BLACKLIST
new file mode 100644
index 000000000..1c761a74e
--- /dev/null
+++ b/tests/auto/client/seatv4/BLACKLIST
@@ -0,0 +1,2 @@
+[animatedCursor]
+b2qt
diff --git a/tests/auto/client/seatv4/tst_seatv4.cpp b/tests/auto/client/seatv4/tst_seatv4.cpp
index 1d6fb6b9c..2e17bef87 100644
--- a/tests/auto/client/seatv4/tst_seatv4.cpp
+++ b/tests/auto/client/seatv4/tst_seatv4.cpp
@@ -81,6 +81,7 @@ private slots:
void bitmapCursor();
void hidpiBitmapCursor();
void hidpiBitmapCursorNonInt();
+ void animatedCursor();
#endif
};
@@ -236,22 +237,20 @@ void tst_seatv4::simpleAxis_data()
{
QTest::addColumn<uint>("axis");
QTest::addColumn<qreal>("value");
- QTest::addColumn<Qt::Orientation>("orientation");
QTest::addColumn<QPoint>("angleDelta");
// Directions in regular windows/linux terms (no "natural" scrolling)
- QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << Qt::Vertical << QPoint{0, -12};
- QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << Qt::Vertical << QPoint{0, 12};
- QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << Qt::Horizontal << QPoint{-12, 0};
- QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << Qt::Horizontal << QPoint{12, 0};
- QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << Qt::Vertical << QPoint{0, 120};
+ 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(Qt::Orientation, orientation);
QFETCH(QPoint, angleDelta);
class WheelWindow : QRasterWindow {
@@ -280,27 +279,18 @@ void tst_seatv4::simpleAxis()
// We didn't press any buttons
QCOMPARE(event->buttons(), Qt::NoButton);
- if (event->orientation() == Qt::Horizontal)
- QCOMPARE(event->delta(), event->angleDelta().x());
- else
- QCOMPARE(event->delta(), event->angleDelta().y());
-
// 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(), event->orientation()));
+ m_events.append(Event{event->pixelDelta(), event->angleDelta()});
}
struct Event // Because I didn't find a convenient way to copy it entirely
{
- // TODO: Constructors can be removed when we start supporting brace-initializers
Event() = default;
- Event(const QPoint &pixelDelta, const QPoint &angleDelta, Qt::Orientation orientation)
- : pixelDelta(pixelDelta), angleDelta(angleDelta), orientation(orientation)
- {}
+
const QPoint pixelDelta;
const QPoint angleDelta; // eights of a degree, positive is upwards, left
- const Qt::Orientation orientation{};
};
QVector<Event> m_events;
};
@@ -323,7 +313,6 @@ void tst_seatv4::simpleAxis()
QTRY_COMPARE(window.m_events.size(), 1);
auto event = window.m_events.takeFirst();
QCOMPARE(event.angleDelta, angleDelta);
- QCOMPARE(event.orientation, orientation);
}
void tst_seatv4::invalidPointerEvents()
@@ -579,6 +568,36 @@ void tst_seatv4::hidpiBitmapCursorNonInt()
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25));
}
+void tst_seatv4::animatedCursor()
+{
+ QRasterWindow window;
+ window.resize(64, 64);
+ window.setCursor(Qt::WaitCursor); // TODO: verify that the theme has an animated wait cursor or skip test
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface());
+
+ // We should get the first buffer without waiting for a frame callback
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
+ QSignalSpy bufferSpy(exec([=] { return cursorSurface(); }), &Surface::bufferCommitted);
+
+ exec([&] {
+ // Make sure no extra buffers have arrived
+ QVERIFY(bufferSpy.empty());
+
+ // The client should send a frame request in order to time animations correctly
+ QVERIFY(!cursorSurface()->m_waitingFrameCallbacks.empty());
+
+ // Tell the client it's time to animate
+ cursorSurface()->sendFrameCallbacks();
+ });
+
+ // Verify that we get a new cursor buffer
+ QTRY_COMPARE(bufferSpy.count(), 1);
+}
+
#endif // QT_CONFIG(cursor)
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
diff --git a/tests/auto/client/seatv5/seatv5.pro b/tests/auto/client/seatv5/seatv5.pro
new file mode 100644
index 000000000..2081845ef
--- /dev/null
+++ b/tests/auto/client/seatv5/seatv5.pro
@@ -0,0 +1,4 @@
+include (../shared/shared.pri)
+
+TARGET = tst_seatv5
+SOURCES += tst_seatv5.cpp
diff --git a/tests/auto/client/seatv5/tst_seatv5.cpp b/tests/auto/client/seatv5/tst_seatv5.cpp
new file mode 100644
index 000000000..636f26081
--- /dev/null
+++ b/tests/auto/client/seatv5/tst_seatv5.cpp
@@ -0,0 +1,590 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mockcompositor.h"
+#include <QtGui/QRasterWindow>
+#include <QtGui/QOpenGLWindow>
+
+using namespace MockCompositor;
+
+class SeatV5Compositor : public DefaultCompositor {
+public:
+ explicit SeatV5Compositor()
+ {
+ exec([this] {
+ m_config.autoConfigure = true;
+
+ removeAll<Seat>();
+
+ uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch;
+ int version = 5;
+ add<Seat>(capabilities, version);
+ });
+ }
+};
+
+class tst_seatv5 : public QObject, private SeatV5Compositor
+{
+ 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 wheelDiscreteScroll();
+
+ // Touch tests
+ void createsTouch();
+ void singleTap();
+ void singleTapFloat();
+ void multiTouch();
+ void multiTouchUpAndMotionFrame();
+};
+
+void tst_seatv5::bindsToSeat()
+{
+ QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1);
+ QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 5);
+}
+
+void tst_seatv5::createsPointer()
+{
+ QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
+ QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 5);
+}
+
+void tst_seatv5::setsCursorOnEnter()
+{
+ QRasterWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ exec([=] {
+ auto *surface = xdgSurface()->m_surface;
+ pointer()->sendEnter(surface, {0, 0});
+ pointer()->sendFrame(surface->resource()->client());
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
+}
+
+void tst_seatv5::usesEnterSerial()
+{
+ QSignalSpy setCursorSpy(exec([=] { return pointer(); }), &Pointer::setCursor);
+ QRasterWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ uint enterSerial = exec([=] {
+ return pointer()->sendEnter(xdgSurface()->m_surface, {0, 0});
+ });
+ QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
+
+ QTRY_COMPARE(setCursorSpy.count(), 1);
+ QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
+}
+
+class WheelWindow : QRasterWindow {
+public:
+ WheelWindow()
+ {
+ resize(64, 64);
+ show();
+ }
+ void wheelEvent(QWheelEvent *event) override
+ {
+ QRasterWindow::wheelEvent(event);
+// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta();
+
+ if (event->phase() != Qt::ScrollUpdate && event->phase() != Qt::NoScrollPhase) {
+ // 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())
+ {
+ }
+ const Qt::ScrollPhase phase{};
+ const QPoint pixelDelta;
+ const QPoint angleDelta; // eights of a degree, positive is upwards, left
+ const Qt::MouseEventSource source{};
+ };
+ QVector<Event> m_events;
+};
+
+void tst_seatv5::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_seatv5::simpleAxis()
+{
+ QFETCH(uint, axis);
+ QFETCH(qreal, value);
+ QFETCH(QPoint, angleDelta);
+
+ WheelWindow window;
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ exec([=] {
+ auto *p = pointer();
+ p->sendEnter(xdgToplevel()->surface(), {32, 32});
+ p->sendFrame(client());
+ p->sendAxis(
+ client(),
+ Pointer::axis(axis),
+ value // Length of vector in surface-local space. i.e. positive is downwards
+ );
+ p->sendFrame(client());
+ });
+
+ QTRY_VERIFY(!window.m_events.empty());
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.phase, Qt::NoScrollPhase);
+ // Pixel delta should only be set if we know it's a high-res input device (which we don't)
+ QCOMPARE(e.pixelDelta, QPoint(0, 0));
+ // There has been no information about what created the event.
+ // Documentation says not synthesized is appropriate in such cases
+ QCOMPARE(e.source, Qt::MouseEventNotSynthesized);
+ QCOMPARE(e.angleDelta, angleDelta);
+ }
+
+ // Sending axis_stop is not mandatory when axis source != finger
+}
+
+void tst_seatv5::fingerScroll()
+{
+ WheelWindow window;
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ exec([=] {
+ auto *p = pointer();
+ auto *c = client();
+ p->sendEnter(xdgToplevel()->surface(), {32, 32});
+ p->sendFrame(c);
+ p->sendAxisSource(c, Pointer::axis_source_finger);
+ p->sendAxis(c, Pointer::axis_vertical_scroll, 10);
+ p->sendFrame(c);
+ });
+
+ QTRY_VERIFY(!window.m_events.empty());
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.phase, Qt::ScrollBegin);
+ QCOMPARE(e.angleDelta, QPoint());
+ QCOMPARE(e.pixelDelta, QPoint());
+ }
+
+ QTRY_VERIFY(!window.m_events.empty());
+ // For some reason we send two ScrollBegins, one for each direction, not sure if this is really
+ // necessary, (could be removed from QtBase, hence the conditional below.
+ if (window.m_events.first().phase == Qt::ScrollBegin) {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.angleDelta, QPoint());
+ QCOMPARE(e.pixelDelta, QPoint());
+ }
+
+ QTRY_VERIFY(!window.m_events.empty());
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.phase, Qt::ScrollUpdate);
+ 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_seatv5::fingerScrollSlow()
+{
+ WheelWindow window;
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ exec([=] {
+ auto *p = pointer();
+ auto *c = client();
+ p->sendEnter(xdgToplevel()->surface(), {32, 32});
+ p->sendFrame(c);
+ // Send 10 really small updates
+ for (int i = 0; i < 10; ++i) {
+ p->sendAxisSource(c, Pointer::axis_source_finger);
+ p->sendAxis(c, Pointer::axis_vertical_scroll, 0.1);
+ p->sendFrame(c);
+ }
+ p->sendAxisStop(c, Pointer::axis_vertical_scroll);
+ p->sendFrame(c);
+ });
+
+ QTRY_VERIFY(!window.m_events.empty());
+ QPoint accumulated;
+ while (window.m_events.first().phase != Qt::ScrollEnd) {
+ auto e = window.m_events.takeFirst();
+ accumulated += e.pixelDelta;
+ QTRY_VERIFY(!window.m_events.empty());
+ }
+ QCOMPARE(accumulated.y(), 1);
+}
+void tst_seatv5::wheelDiscreteScroll()
+{
+ WheelWindow window;
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ exec([=] {
+ auto *p = pointer();
+ auto *c = client();
+ p->sendEnter(xdgToplevel()->surface(), {32, 32});
+ p->sendFrame(c);
+ p->sendAxisSource(c, Pointer::axis_source_wheel);
+ p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards
+ p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0);
+ p->sendFrame(c);
+ });
+
+ QTRY_VERIFY(!window.m_events.empty());
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.phase, Qt::NoScrollPhase);
+ 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));
+ }
+}
+
+void tst_seatv5::createsTouch()
+{
+ QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1);
+ QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().first()->version(), 5);
+}
+
+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->touchPoints())
+ {
+ }
+ const QEvent::Type type{};
+ const Qt::TouchPointStates touchPointStates{};
+ const QList<QTouchEvent::TouchPoint> touchPoints;
+ };
+ QVector<Event> m_events;
+};
+
+void tst_seatv5::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, Qt::TouchPointState::TouchPointPressed);
+ QCOMPARE(e.touchPoints.length(), 1);
+ QCOMPARE(e.touchPoints.first().pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top()));
+ }
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.type, QEvent::TouchEnd);
+ QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased);
+ QCOMPARE(e.touchPoints.length(), 1);
+ QCOMPARE(e.touchPoints.first().pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top()));
+ }
+}
+
+void tst_seatv5::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, Qt::TouchPointState::TouchPointPressed);
+ QCOMPARE(e.touchPoints.length(), 1);
+ QCOMPARE(e.touchPoints.first().pos(), 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, Qt::TouchPointState::TouchPointReleased);
+ QCOMPARE(e.touchPoints.length(), 1);
+ QCOMPARE(e.touchPoints.first().pos(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top()));
+ }
+}
+
+void tst_seatv5::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::touchPoints()
+ // 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, Qt::TouchPointState::TouchPointPressed);
+ QCOMPARE(e.touchPoints.length(), 2);
+
+ QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointPressed);
+ QCOMPARE(e.touchPoints[0].pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top()));
+
+ QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointPressed);
+ QCOMPARE(e.touchPoints[1].pos(), QPointF(48-window.frameMargins().left(), 48-window.frameMargins().top()));
+ }
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.type, QEvent::TouchUpdate);
+ QCOMPARE(e.touchPoints.length(), 2);
+
+ QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointMoved);
+ QCOMPARE(e.touchPoints[0].pos(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top()));
+
+ QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointMoved);
+ QCOMPARE(e.touchPoints[1].pos(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top()));
+ }
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.type, QEvent::TouchUpdate);
+ QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased | Qt::TouchPointState::TouchPointStationary);
+ QCOMPARE(e.touchPoints.length(), 2);
+
+ QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
+ QCOMPARE(e.touchPoints[0].pos(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top()));
+
+ QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointStationary);
+ QCOMPARE(e.touchPoints[1].pos(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top()));
+ }
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.type, QEvent::TouchEnd);
+ QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased);
+ QCOMPARE(e.touchPoints.length(), 1);
+ QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
+ QCOMPARE(e.touchPoints[0].pos(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top()));
+ }
+}
+
+void tst_seatv5::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(), Qt::TouchPointState::TouchPointPressed);
+ QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointPressed);
+ }
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.type, QEvent::TouchUpdate);
+ QCOMPARE(e.touchPoints.length(), 2);
+ QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
+ QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointMoved);
+ }
+ {
+ auto e = window.m_events.takeFirst();
+ QCOMPARE(e.type, QEvent::TouchEnd);
+ QCOMPARE(e.touchPoints.length(), 1);
+ QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
+ }
+ QVERIFY(window.m_events.empty());
+}
+
+QCOMPOSITOR_TEST_MAIN(tst_seatv5)
+#include "tst_seatv5.moc"
diff --git a/tests/auto/client/shared/corecompositor.h b/tests/auto/client/shared/corecompositor.h
index 875b7d050..254465ee6 100644
--- a/tests/auto/client/shared/corecompositor.h
+++ b/tests/auto/client/shared/corecompositor.h
@@ -125,6 +125,23 @@ public:
}
/*!
+ * \brief Returns the nth global with the given type, if any
+ */
+ template<typename global_type>
+ global_type *get(int index)
+ {
+ warnIfNotLockedByThread(Q_FUNC_INFO);
+ for (auto *global : qAsConst(m_globals)) {
+ if (auto *casted = qobject_cast<global_type *>(global)) {
+ if (index--)
+ continue;
+ return casted;
+ }
+ }
+ return nullptr;
+ }
+
+ /*!
* \brief Returns all globals with the given type, if any
*/
template<typename global_type>
diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp
index 729d481f8..b0be2cb4e 100644
--- a/tests/auto/client/shared/coreprotocol.cpp
+++ b/tests/auto/client/shared/coreprotocol.cpp
@@ -191,12 +191,15 @@ 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) {
- // TODO: Add support for touch
- Q_ASSERT(~capabilities & capability_touch);
-
m_capabilities = capabilities;
if (m_capabilities & capability_pointer) {
@@ -207,6 +210,14 @@ void Seat::setCapabilities(uint capabilities) {
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));
@@ -234,9 +245,24 @@ void Seat::seat_get_pointer(Resource *resource, uint32_t id)
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_pointer) {
+ 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";
@@ -314,6 +340,39 @@ void Pointer::sendAxis(wl_client *client, axis axis, qreal value)
send_axis(r->handle, time, axis, val);
}
+void Pointer::sendAxisDiscrete(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int discrete)
+{
+ // TODO: assert v5 or newer
+ const auto pointerResources = resourceMap().values(client);
+ for (auto *r : pointerResources)
+ send_axis_discrete(r->handle, axis, discrete);
+}
+
+void Pointer::sendAxisSource(wl_client *client, QtWaylandServer::wl_pointer::axis_source source)
+{
+ // TODO: assert v5 or newer
+ const auto pointerResources = resourceMap().values(client);
+ for (auto *r : pointerResources)
+ send_axis_source(r->handle, source);
+}
+
+void Pointer::sendAxisStop(wl_client *client, QtWaylandServer::wl_pointer::axis axis)
+{
+ // TODO: assert v5 or newer
+ auto time = m_seat->m_compositor->currentTimeMilliseconds();
+ const auto pointerResources = resourceMap().values(client);
+ for (auto *r : pointerResources)
+ send_axis_stop(r->handle, time, axis);
+}
+
+void Pointer::sendFrame(wl_client *client)
+{
+ //TODO: assert version 5 or newer?
+ const auto pointerResources = resourceMap().values(client);
+ for (auto *r : pointerResources)
+ send_frame(r->handle);
+}
+
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);
@@ -338,6 +397,52 @@ void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resourc
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);
+}
+
uint Keyboard::sendEnter(Surface *surface)
{
auto serial = m_seat->m_compositor->nextSerial();
@@ -345,6 +450,7 @@ uint Keyboard::sendEnter(Surface *surface)
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_enter(r->handle, serial, surface->resource()->handle, QByteArray());
+ m_enteredSurface = surface;
return serial;
}
@@ -355,6 +461,7 @@ uint Keyboard::sendLeave(Surface *surface)
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_leave(r->handle, serial, surface->resource()->handle);
+ m_enteredSurface = nullptr;
return serial;
}
diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h
index f92842e02..a12d22d36 100644
--- a/tests/auto/client/shared/coreprotocol.h
+++ b/tests/auto/client/shared/coreprotocol.h
@@ -38,6 +38,7 @@ namespace MockCompositor {
class WlCompositor;
class Output;
class Pointer;
+class Touch;
class Keyboard;
class CursorRole;
class ShmPool;
@@ -259,7 +260,7 @@ class Seat : public Global, public QtWaylandServer::wl_seat
{
Q_OBJECT
public:
- explicit Seat(CoreCompositor *compositor, uint capabilities, int version = 4);
+ explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch, int version = 5);
~Seat() override;
void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead
void send_capabilities(uint capabilities) = delete; // Use wrapper instead
@@ -270,6 +271,9 @@ public:
Pointer* m_pointer = nullptr;
QVector<Pointer *> m_oldPointers;
+ Touch* m_touch = nullptr;
+ QVector<Touch *> m_oldTouchs;
+
Keyboard* m_keyboard = nullptr;
QVector<Keyboard *> m_oldKeyboards;
@@ -282,8 +286,8 @@ protected:
}
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_get_touch(Resource *resource, uint32_t id) override;
// void seat_release(Resource *resource) override;
};
@@ -302,6 +306,10 @@ public:
void sendMotion(wl_client *client, const QPointF &position);
uint sendButton(wl_client *client, uint button, uint state);
void sendAxis(wl_client *client, axis axis, qreal value);
+ void sendAxisDiscrete(wl_client *client, axis axis, int discrete);
+ void sendAxisSource(wl_client *client, axis_source source);
+ void sendAxisStop(wl_client *client, axis axis);
+ void sendFrame(wl_client *client);
Seat *m_seat = nullptr;
QVector<uint> m_enterSerials;
@@ -326,6 +334,19 @@ public:
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);
+
+ Seat *m_seat = nullptr;
+};
+
class Keyboard : public QObject, public QtWaylandServer::wl_keyboard
{
Q_OBJECT
@@ -336,6 +357,7 @@ public:
uint sendLeave(Surface *surface);
uint sendKey(wl_client *client, uint key, uint state);
Seat *m_seat = nullptr;
+ Surface *m_enteredSurface = nullptr;
};
class Shm : public Global, public QtWaylandServer::wl_shm
diff --git a/tests/auto/client/shared/datadevice.h b/tests/auto/client/shared/datadevice.h
index a96da86f0..98e780b22 100644
--- a/tests/auto/client/shared/datadevice.h
+++ b/tests/auto/client/shared/datadevice.h
@@ -65,7 +65,6 @@ public:
~DataDevice() override;
void send_data_offer(::wl_resource *resource) = delete;
DataOffer *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {});
- DataOffer *sendDataOffer(const QStringList &mimeTypes = {});
void send_selection(::wl_resource *resource) = delete;
void sendSelection(DataOffer *offer);
diff --git a/tests/auto/client/shared/mockcompositor.cpp b/tests/auto/client/shared/mockcompositor.cpp
index 0711c5d8e..e44cea63c 100644
--- a/tests/auto/client/shared/mockcompositor.cpp
+++ b/tests/auto/client/shared/mockcompositor.cpp
@@ -41,7 +41,7 @@ DefaultCompositor::DefaultCompositor()
add<SubCompositor>();
auto *output = add<Output>();
output->m_data.physicalSize = output->m_data.mode.physicalSizeForDpi(96);
- add<Seat>(Seat::capability_pointer | Seat::capability_keyboard);
+ add<Seat>(Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch);
add<XdgWmBase>();
add<Shm>();
// TODO: other shells, viewporter, xdgoutput etc
diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h
index f17c93426..deef1f909 100644
--- a/tests/auto/client/shared/mockcompositor.h
+++ b/tests/auto/client/shared/mockcompositor.h
@@ -36,10 +36,16 @@
#include <QtGui/QGuiApplication>
-#ifndef BTN_LEFT
// As defined in linux/input-event-codes.h
+#ifndef BTN_LEFT
#define BTN_LEFT 0x110
#endif
+#ifndef BTN_RIGHT
+#define BTN_RIGHT 0x111
+#endif
+#ifndef BTN_MIDDLE
+#define BTN_MIDDLE 0x112
+#endif
namespace MockCompositor {
@@ -55,6 +61,7 @@ public:
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; }
uint sendXdgShellPing();
diff --git a/tests/auto/client/shared_old/mockcompositor.cpp b/tests/auto/client/shared_old/mockcompositor.cpp
index 96dc059e7..9553076dd 100644
--- a/tests/auto/client/shared_old/mockcompositor.cpp
+++ b/tests/auto/client/shared_old/mockcompositor.cpp
@@ -221,8 +221,8 @@ QSharedPointer<MockSurface> MockCompositor::surface()
QSharedPointer<MockSurface> result;
lock();
{
- QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
- foreach (Impl::Surface *surface, surfaces) {
+ const QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
+ for (Impl::Surface *surface : surfaces) {
// we don't want to mistake the cursor surface for a window surface
if (surface->isMapped()) {
result = surface->mockSurface();
diff --git a/tests/auto/client/shared_old/mocksurface.cpp b/tests/auto/client/shared_old/mocksurface.cpp
index 81a5edbd0..e9df5f907 100644
--- a/tests/auto/client/shared_old/mocksurface.cpp
+++ b/tests/auto/client/shared_old/mocksurface.cpp
@@ -149,11 +149,10 @@ void Surface::surface_commit(Resource *resource)
}
}
- foreach (wl_resource *frameCallback, m_frameCallbackList) {
+ for (wl_resource *frameCallback : qExchange(m_frameCallbackList, {})) {
wl_callback_send_done(frameCallback, m_compositor->time());
wl_resource_destroy(frameCallback);
}
- m_frameCallbackList.clear();
}
}
diff --git a/tests/auto/client/surface/tst_surface.cpp b/tests/auto/client/surface/tst_surface.cpp
index f9dcec581..b8a65f159 100644
--- a/tests/auto/client/surface/tst_surface.cpp
+++ b/tests/auto/client/surface/tst_surface.cpp
@@ -28,7 +28,9 @@
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
+#if QT_CONFIG(opengl)
#include <QtGui/QOpenGLWindow>
+#endif
using namespace MockCompositor;
@@ -39,7 +41,9 @@ private slots:
void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
void createDestroySurface();
void waitForFrameCallbackRaster();
+#if QT_CONFIG(opengl)
void waitForFrameCallbackGl();
+#endif
void negotiateShmFormat();
// Subsurfaces
@@ -93,6 +97,7 @@ void tst_surface::waitForFrameCallbackRaster()
}
}
+#if QT_CONFIG(opengl)
void tst_surface::waitForFrameCallbackGl()
{
QSKIP("TODO: This currently fails, needs a fix");
@@ -133,6 +138,7 @@ void tst_surface::waitForFrameCallbackGl()
bufferSpy.removeFirst();
}
}
+#endif // QT_CONFIG(opengl)
void tst_surface::negotiateShmFormat()
{
diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp
index 9b18abdc3..ac5c24988 100644
--- a/tests/auto/client/xdgshell/tst_xdgshell.cpp
+++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp
@@ -29,6 +29,8 @@
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
+#include <QtGui/qpa/qplatformnativeinterface.h>
+#include <QtWaylandClient/private/wayland-wayland-client-protocol.h>
using namespace MockCompositor;
@@ -47,6 +49,7 @@ private slots:
void pongs();
void minMaxSize();
void windowGeometry();
+ void foreignSurface();
};
void tst_xdgshell::showMinimized()
@@ -211,12 +214,13 @@ void tst_xdgshell::popup()
uint clickSerial = exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
+ auto *c = client();
p->sendEnter(surface, {100, 100});
-// p->sendFrame(); //TODO: uncomment when we support seat v5
+ p->sendFrame(c);
uint serial = p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
- p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
+ p->sendButton(c, BTN_LEFT, Pointer::button_state_released);
return serial;
-// p->sendFrame(); //TODO: uncomment when we support seat v5
+ p->sendFrame(c);
});
QTRY_VERIFY(window.m_popup);
@@ -295,13 +299,14 @@ void tst_xdgshell::tooltipOnPopup()
exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
+ auto *c = client();
p->sendEnter(surface, {100, 100});
-// p->sendFrame(); //TODO: uncomment when we support seat v5
+ p->sendFrame(c);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
-// p->sendFrame();
+ p->sendFrame(c);
p->sendLeave(surface);
-// p->sendFrame();
+ p->sendFrame(c);
});
QCOMPOSITOR_TRY_VERIFY(xdgPopup());
@@ -312,11 +317,12 @@ void tst_xdgshell::tooltipOnPopup()
exec([=] {
auto *surface = xdgPopup()->surface();
auto *p = pointer();
+ auto *c = client();
p->sendEnter(surface, {100, 100});
-// p->sendFrame();
+ p->sendFrame(c);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
-// p->sendFrame();
+ p->sendFrame(c);
});
QCOMPOSITOR_TRY_VERIFY(xdgPopup(1));
@@ -377,13 +383,14 @@ void tst_xdgshell::switchPopups()
exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
+ auto *c = client();
p->sendEnter(surface, {100, 100});
-// p->sendFrame(); //TODO: uncomment when we support seat v5
- p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
- p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
-// p->sendFrame();
+ 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();
+ p->sendFrame(c);
});
QCOMPOSITOR_TRY_VERIFY(xdgPopup());
@@ -396,11 +403,12 @@ void tst_xdgshell::switchPopups()
exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
+ auto *c = client();
p->sendEnter(surface, {100, 100});
-// p->sendFrame();
- p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
- p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
-// p->sendFrame();
+ 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
@@ -473,10 +481,41 @@ void tst_xdgshell::windowGeometry()
exec([=] { xdgToplevel()->sendCompleteConfigure(); });
- QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committed.windowGeometry, QRect(QPoint(0, 0), window.frameGeometry().size()));
+ 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), window.frameGeometry().size()));
+ 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.count(), 1);
+
+ wl_surface_destroy(foreignSurface);
}
QCOMPOSITOR_TEST_MAIN(tst_xdgshell)
diff --git a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
index 9885ee2bc..e44475de7 100644
--- a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
+++ b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
@@ -413,11 +413,11 @@ void tst_WaylandClientXdgShellV6::dontSpamExposeEvents()
QSharedPointer<MockSurface> surface;
QTRY_VERIFY(surface = m_compositor->surface());
- QTRY_VERIFY(window.exposeEventCount == 0);
+ QTRY_COMPARE(window.exposeEventCount, 0);
m_compositor->sendShellSurfaceConfigure(surface);
QTRY_VERIFY(window.isExposed());
- QTRY_VERIFY(window.exposeEventCount == 1);
+ QTRY_COMPARE(window.exposeEventCount, 1);
}
int main(int argc, char **argv)