summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorPaul Olav Tvete <paul.tvete@qt.io>2019-03-21 11:42:15 +0100
committerPaul Olav Tvete <paul.tvete@qt.io>2019-03-21 11:57:15 +0100
commit77e8ee63dc9ad0a4c139f35f8cf078d1a5bd315c (patch)
tree499f94df98fc25bca2982fba4dd67457f42bd6b9 /tests
parentdf3a1761af2f20d59ae09a7adaa2f5b959047687 (diff)
parentc0905957be0d7db90c9d9f05069a259575753dfe (diff)
Merge remote-tracking branch 'qt/5.13' into dev
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/client/datadevicev1/tst_datadevicev1.cpp90
-rw-r--r--tests/auto/client/seatv4/seatv4.pro5
-rw-r--r--tests/auto/client/seatv4/tst_seatv4.cpp292
-rw-r--r--tests/auto/client/shared/coreprotocol.cpp26
-rw-r--r--tests/auto/client/shared/coreprotocol.h6
-rw-r--r--tests/auto/client/shared/datadevice.cpp54
-rw-r--r--tests/auto/client/shared/datadevice.h36
-rw-r--r--tests/auto/client/shared/mockcompositor.h1
-rw-r--r--tests/auto/client/shared/xdgshell.cpp1
-rw-r--r--tests/auto/client/shared/xdgshell.h5
-rw-r--r--tests/auto/client/shared_old/mockcompositor.cpp24
-rw-r--r--tests/auto/client/shared_old/mockcompositor.h5
-rw-r--r--tests/auto/client/xdgshell/tst_xdgshell.cpp89
13 files changed, 558 insertions, 76 deletions
diff --git a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp
index a85b10c74..fe68d520d 100644
--- a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp
+++ b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp
@@ -47,7 +47,7 @@ public:
add<DataDeviceManager>(dataDeviceVersion);
});
}
- DataDevice *dataDevice() { return get<Seat>()->dataDevice(); }
+ DataDevice *dataDevice() { return get<DataDeviceManager>()->deviceFor(get<Seat>()); }
};
class tst_datadevicev1 : public QObject, private DataDeviceCompositor
@@ -58,6 +58,8 @@ private slots:
void initTestCase();
void pasteAscii();
void pasteUtf8();
+ void destroysPreviousSelection();
+ void destroysSelectionWithSurface();
};
void tst_datadevicev1::initTestCase()
@@ -69,7 +71,8 @@ void tst_datadevicev1::initTestCase()
QCOMPOSITOR_TRY_VERIFY(keyboard());
QCOMPOSITOR_TRY_VERIFY(dataDevice());
- QCOMPOSITOR_TRY_COMPARE(dataDevice()->resource()->version(), dataDeviceVersion);
+ QCOMPOSITOR_TRY_VERIFY(dataDevice()->resourceMap().contains(client()));
+ QCOMPOSITOR_TRY_COMPARE(dataDevice()->resourceMap().value(client())->version(), dataDeviceVersion);
}
void tst_datadevicev1::pasteAscii()
@@ -86,7 +89,8 @@ void tst_datadevicev1::pasteAscii()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([&] {
- auto *offer = new DataOffer(client(), dataDeviceVersion); // Cleaned up by destroy_resource
+ auto *client = xdgSurface()->resource()->client();
+ auto *offer = dataDevice()->sendDataOffer(client, {"text/plain"});
connect(offer, &DataOffer::receive, [](QString mimeType, int fd) {
QFile file;
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
@@ -94,16 +98,14 @@ void tst_datadevicev1::pasteAscii()
file.write(QByteArray("normal ascii"));
file.close();
});
- dataDevice()->sendDataOffer(offer);
- offer->send_offer("text/plain");
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()->sendButton(client(), BTN_LEFT, 1);
- pointer()->sendButton(client(), BTN_LEFT, 0);
+ pointer()->sendButton(client, BTN_LEFT, 1);
+ pointer()->sendButton(client, BTN_LEFT, 0);
});
QTRY_COMPARE(window.m_text, "normal ascii");
}
@@ -122,7 +124,8 @@ void tst_datadevicev1::pasteUtf8()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([&] {
- auto *offer = new DataOffer(client(), dataDeviceVersion); // Cleaned up by destroy_resource
+ auto *client = xdgSurface()->resource()->client();
+ auto *offer = dataDevice()->sendDataOffer(client, {"text/plain", "text/plain;charset=utf-8"});
connect(offer, &DataOffer::receive, [](QString mimeType, int fd) {
QFile file;
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
@@ -130,20 +133,81 @@ void tst_datadevicev1::pasteUtf8()
file.write(QByteArray("face with tears of joy: 😂"));
file.close();
});
- dataDevice()->sendDataOffer(offer);
- offer->send_offer("text/plain");
- offer->send_offer("text/plain;charset=utf-8");
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()->sendButton(client(), BTN_LEFT, 1);
- pointer()->sendButton(client(), BTN_LEFT, 0);
+ pointer()->sendButton(client, BTN_LEFT, 1);
+ pointer()->sendButton(client, BTN_LEFT, 0);
});
QTRY_COMPARE(window.m_text, "face with tears of joy: 😂");
}
+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);
+}
+
QCOMPOSITOR_TEST_MAIN(tst_datadevicev1)
#include "tst_datadevicev1.moc"
diff --git a/tests/auto/client/seatv4/seatv4.pro b/tests/auto/client/seatv4/seatv4.pro
index c02db5855..7a86cbf03 100644
--- a/tests/auto/client/seatv4/seatv4.pro
+++ b/tests/auto/client/seatv4/seatv4.pro
@@ -1,4 +1,9 @@
include (../shared/shared.pri)
+qtConfig(cursor) {
+ QMAKE_USE += wayland-cursor
+ QT += gui-private
+}
+
TARGET = tst_seatv4
SOURCES += tst_seatv4.cpp
diff --git a/tests/auto/client/seatv4/tst_seatv4.cpp b/tests/auto/client/seatv4/tst_seatv4.cpp
index f1e948ee2..7dc2e727a 100644
--- a/tests/auto/client/seatv4/tst_seatv4.cpp
+++ b/tests/auto/client/seatv4/tst_seatv4.cpp
@@ -30,6 +30,12 @@
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
+#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;
@@ -59,16 +65,22 @@ class tst_seatv4 : public QObject, private SeatV4Compositor
private slots:
void cleanup();
void bindsToSeat();
+ void keyboardKeyPress();
+#if QT_CONFIG(cursor)
void createsPointer();
void setsCursorOnEnter();
void usesEnterSerial();
+ void focusDestruction();
void mousePress();
void simpleAxis_data();
void simpleAxis();
void invalidPointerEvents();
void scaledCursor();
-
- void keyboardKeyPress();
+ void unscaledFallbackCursor();
+ void bitmapCursor();
+ void hidpiBitmapCursor();
+ void hidpiBitmapCursorNonInt();
+#endif
};
void tst_seatv4::cleanup()
@@ -83,6 +95,31 @@ void tst_seatv4::bindsToSeat()
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);
@@ -97,7 +134,7 @@ void tst_seatv4::setsCursorOnEnter()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
- QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface());
}
void tst_seatv4::usesEnterSerial()
@@ -111,12 +148,43 @@ void tst_seatv4::usesEnterSerial()
uint enterSerial = exec([=] {
return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
});
- QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QTRY_COMPARE(setCursorSpy.count(), 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.count(), 0);
+
+ uint enterSerial = exec([=] {
+ return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
+ });
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface());
+ QTRY_COMPARE(setCursorSpy.count(), 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.count(), 0);
+}
+
void tst_seatv4::mousePress()
{
class Window : public QRasterWindow {
@@ -253,9 +321,42 @@ void tst_seatv4::invalidPointerEvents()
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 QVector<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) : 32;
+}
+
void tst_seatv4::scaledCursor()
{
- QSKIP("Currently broken and should be fixed");
+ 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;
@@ -270,47 +371,190 @@ void tst_seatv4::scaledCursor()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
- QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
- QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()->m_committed.buffer);
- QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.bufferScale, 1);
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface());
+ QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
+ QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
QSize unscaledPixelSize = exec([=] {
- return pointer()->cursorSurface()->m_committed.buffer->size();
+ return cursorSurface()->m_committed.buffer->size();
});
exec([=] {
- auto *surface = pointer()->cursorSurface();
+ auto *surface = cursorSurface();
surface->sendEnter(getAll<Output>()[1]);
surface->sendLeave(getAll<Output>()[0]);
});
- QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.buffer->size(), unscaledPixelSize * 2);
+ 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::keyboardKeyPress()
+void tst_seatv4::unscaledFallbackCursor()
{
- class Window : public QRasterWindow {
- public:
- void keyPressEvent(QKeyEvent *) override { m_pressed = true; }
- bool m_pressed = false;
- };
+ const uint defaultSize = defaultCursorSize();
+ if (!supportsCursorSizes({defaultSize}))
+ QSKIP("Default cursor size not supported");
- Window window;
+ 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();
+ });
- uint keyCode = 80; // arbitrarily chosen
+ 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([&] {
- auto *surface = xdgSurface()->m_surface;
- keyboard()->sendEnter(surface);
- keyboard()->sendKey(client(), keyCode, Keyboard::key_state_pressed);
- keyboard()->sendKey(client(), keyCode, Keyboard::key_state_released);
+ OutputData d;
+ d.scale = 2;
+ d.position = {1920, 0};
+ add<Output>(d);
});
- QTRY_VERIFY(window.m_pressed);
+
+ 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));
+}
+
+#endif // QT_CONFIG(cursor)
+
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
#include "tst_seatv4.moc"
diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp
index b37f7f29c..b10377c2c 100644
--- a/tests/auto/client/shared/coreprotocol.cpp
+++ b/tests/auto/client/shared/coreprotocol.cpp
@@ -258,18 +258,20 @@ 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());
- m_enterSerial = m_seat->m_compositor->nextSerial();
+
+ uint serial = m_seat->m_compositor->nextSerial();
+ m_enterSerials << serial;
+ m_cursorRole = nullptr; // 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, m_enterSerial, surface->resource()->handle, x ,y);
- return m_enterSerial;
+ wl_pointer::send_enter(r->handle, serial, surface->resource()->handle, x ,y);
+ return serial;
}
uint Pointer::sendLeave(Surface *surface)
{
- m_enterSerial = 0;
uint serial = m_seat->m_compositor->nextSerial();
wl_client *client = surface->resource()->client();
@@ -348,20 +350,24 @@ void Pointer::sendFrame(wl_client *client)
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);
- Q_UNUSED(hotspot_x);
- Q_UNUSED(hotspot_y);
auto *s = fromResource<Surface>(surface);
QVERIFY(s);
if (s->m_role) {
- auto *cursorRole = CursorRole::fromSurface(s);
- QVERIFY(cursorRole);
- QVERIFY(cursorRole == m_cursorRole);
+ 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;
}
-// QCOMPARE(serial, m_enterSerial); //TODO: uncomment when this bug is fixed
+
+ // 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);
}
diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h
index 94876f15f..3563331c7 100644
--- a/tests/auto/client/shared/coreprotocol.h
+++ b/tests/auto/client/shared/coreprotocol.h
@@ -250,9 +250,6 @@ public:
Keyboard* m_keyboard = nullptr;
QVector<Keyboard *> m_oldKeyboards;
- DataDevice *dataDevice() { return m_dataDevice.data(); }
- QScopedPointer<DataDevice> m_dataDevice;
-
uint m_capabilities = 0;
protected:
@@ -288,7 +285,8 @@ public:
void sendFrame(wl_client *client);
Seat *m_seat = nullptr;
- uint m_enterSerial = 0;
+ QVector<uint> m_enterSerials;
+ QPoint m_hotspot;
signals:
void setCursor(uint serial); //TODO: add arguments?
diff --git a/tests/auto/client/shared/datadevice.cpp b/tests/auto/client/shared/datadevice.cpp
index c136c7596..dfa18952c 100644
--- a/tests/auto/client/shared/datadevice.cpp
+++ b/tests/auto/client/shared/datadevice.cpp
@@ -30,29 +30,60 @@
namespace MockCompositor {
+bool DataDeviceManager::isClean()
+{
+ for (auto *device : qAsConst(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);
- QVERIFY(!seat->m_dataDevice);
- seat->m_dataDevice.reset(new DataDevice(resource->client(), id, resource->version()));
+ auto *device = deviceFor(seat);
+ device->add(resource->client(), id, resource->version());
}
DataDevice::~DataDevice()
{
- // If the client hasn't deleted the wayland object, just ignore subsequent events
- if (auto *r = resource()->handle)
- wl_resource_set_implementation(r, nullptr, nullptr, nullptr);
+ // 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);
}
-void DataDevice::sendDataOffer(DataOffer *offer)
+DataOffer *DataDevice::sendDataOffer(wl_client *client, const QStringList &mimeTypes)
{
- wl_data_device::send_data_offer(offer->resource()->handle);
+ Q_ASSERT(client);
+ auto *offer = new DataOffer(this, client, m_manager->m_version);
+ 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)
{
- wl_data_device::send_selection(offer->resource()->handle);
+ 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 DataOffer::data_offer_destroy_resource(Resource *resource)
@@ -67,4 +98,11 @@ void DataOffer::data_offer_receive(Resource *resource, const QString &mime_type,
emit receive(mime_type, fd);
}
+void DataOffer::data_offer_destroy(QtWaylandServer::wl_data_offer::Resource *resource)
+{
+ bool removed = m_dataDevice->m_sentSelectionOffers.removeOne(this);
+ QVERIFY(removed);
+ wl_resource_destroy(resource->handle);
+}
+
} // namespace MockCompositor
diff --git a/tests/auto/client/shared/datadevice.h b/tests/auto/client/shared/datadevice.h
index 4613db776..a96da86f0 100644
--- a/tests/auto/client/shared/datadevice.h
+++ b/tests/auto/client/shared/datadevice.h
@@ -42,8 +42,14 @@ class DataDeviceManager : public Global, public QtWaylandServer::wl_data_device_
public:
explicit DataDeviceManager(CoreCompositor *compositor, int version = 1)
: QtWaylandServer::wl_data_device_manager(compositor->m_display, version)
+ , m_version(version)
{}
- QVector<DataDevice *> m_dataDevices;
+ ~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;
protected:
void data_device_manager_get_data_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override;
@@ -52,24 +58,42 @@ protected:
class DataDevice : public QtWaylandServer::wl_data_device
{
public:
- explicit DataDevice(::wl_client *client, int id, int version)
- : QtWaylandServer::wl_data_device(client, id, version)
+ explicit DataDevice(DataDeviceManager *manager, Seat *seat)
+ : m_manager(manager)
+ , m_seat(seat)
{}
~DataDevice() override;
void send_data_offer(::wl_resource *resource) = delete;
- void sendDataOffer(DataOffer *offer);
+ DataOffer *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {});
+ DataOffer *sendDataOffer(const QStringList &mimeTypes = {});
+
void send_selection(::wl_resource *resource) = delete;
void sendSelection(DataOffer *offer);
+
+ DataDeviceManager *m_manager = nullptr;
+ Seat *m_seat = nullptr;
+ QVector<DataOffer *> m_sentSelectionOffers;
+
+protected:
+ 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(::wl_client *client, int version)
+ 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);
@@ -77,7 +101,7 @@ 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;
+ void data_offer_destroy(Resource *resource) override;
};
} // namespace MockCompositor
diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h
index ddbd7f342..75ef1eaea 100644
--- a/tests/auto/client/shared/mockcompositor.h
+++ b/tests/auto/client/shared/mockcompositor.h
@@ -54,6 +54,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; }
+ 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();
void xdgPingAndWaitForPong();
diff --git a/tests/auto/client/shared/xdgshell.cpp b/tests/auto/client/shared/xdgshell.cpp
index 9437688ad..13acc01e2 100644
--- a/tests/auto/client/shared/xdgshell.cpp
+++ b/tests/auto/client/shared/xdgshell.cpp
@@ -236,6 +236,7 @@ void XdgPopup::xdg_popup_destroy(Resource *resource) {
}
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
index ca19c293f..618babde7 100644
--- a/tests/auto/client/shared/xdgshell.h
+++ b/tests/auto/client/shared/xdgshell.h
@@ -130,8 +130,9 @@ protected:
void xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) override;
};
-class XdgPopup : public QtWaylandServer::xdg_popup
+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);
@@ -141,6 +142,8 @@ public:
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;
diff --git a/tests/auto/client/shared_old/mockcompositor.cpp b/tests/auto/client/shared_old/mockcompositor.cpp
index f6a875747..0dfaef5ea 100644
--- a/tests/auto/client/shared_old/mockcompositor.cpp
+++ b/tests/auto/client/shared_old/mockcompositor.cpp
@@ -220,12 +220,14 @@ 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();
- break;
+ {
+ 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();
+ break;
+ }
}
}
unlock();
@@ -307,7 +309,7 @@ void *MockCompositor::run(void *data)
{
MockCompositor *controller = static_cast<MockCompositor *>(data);
- Impl::Compositor compositor;
+ Impl::Compositor compositor(controller);
controller->m_compositor = &compositor;
controller->m_waitCondition.wakeOne();
@@ -332,8 +334,8 @@ void *MockCompositor::run(void *data)
namespace Impl {
-Compositor::Compositor()
- : m_display(wl_display_create())
+Compositor::Compositor(MockCompositor *mockCompositor)
+ : m_mockCompositor(mockCompositor), m_display(wl_display_create())
{
if (wl_display_add_socket(m_display, 0)) {
fprintf(stderr, "Fatal: Failed to open server socket\n");
@@ -445,15 +447,19 @@ uint32_t Compositor::nextSerial()
void Compositor::addSurface(Surface *surface)
{
+ m_mockCompositor->lock();
m_surfaces << surface;
+ m_mockCompositor->unlock();
}
void Compositor::removeSurface(Surface *surface)
{
+ m_mockCompositor->lock();
m_surfaces.removeOne(surface);
m_keyboard->handleSurfaceDestroyed(surface);
m_pointer->handleSurfaceDestroyed(surface);
m_fullScreenShellV1->removeSurface(surface);
+ m_mockCompositor->unlock();
}
Surface *Compositor::resolveSurface(const QVariant &v)
diff --git a/tests/auto/client/shared_old/mockcompositor.h b/tests/auto/client/shared_old/mockcompositor.h
index 404a18e75..2433ac005 100644
--- a/tests/auto/client/shared_old/mockcompositor.h
+++ b/tests/auto/client/shared_old/mockcompositor.h
@@ -45,6 +45,8 @@
#include <QVector>
#include <QWaitCondition>
+class MockCompositor;
+
namespace Impl {
typedef void (**Implementation)(void);
@@ -63,7 +65,7 @@ class XdgShellV6;
class Compositor
{
public:
- Compositor();
+ Compositor(MockCompositor *mockCompositor);
~Compositor();
int fileDescriptor() const { return m_fd; }
@@ -114,6 +116,7 @@ private:
void initShm();
+ MockCompositor *m_mockCompositor = nullptr;
QRect m_outputGeometry;
wl_display *m_display = nullptr;
diff --git a/tests/auto/client/xdgshell/tst_xdgshell.cpp b/tests/auto/client/xdgshell/tst_xdgshell.cpp
index 32b62689e..6efffc8a4 100644
--- a/tests/auto/client/xdgshell/tst_xdgshell.cpp
+++ b/tests/auto/client/xdgshell/tst_xdgshell.cpp
@@ -43,6 +43,7 @@ private slots:
void configureStates();
void popup();
void tooltipOnPopup();
+ void switchPopups();
void pongs();
void minMaxSize();
void windowGeometry();
@@ -332,6 +333,94 @@ void tst_xdgshell::tooltipOnPopup()
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); }
+ QVector<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();
+ 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->sendLeave(surface);
+// p->sendFrame();
+ });
+
+ 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();
+ 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();
+ });
+
+ // 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::pongs()
{
QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong);