diff options
author | Paul Olav Tvete <paul.tvete@qt.io> | 2019-03-21 11:42:15 +0100 |
---|---|---|
committer | Paul Olav Tvete <paul.tvete@qt.io> | 2019-03-21 11:57:15 +0100 |
commit | 77e8ee63dc9ad0a4c139f35f8cf078d1a5bd315c (patch) | |
tree | 499f94df98fc25bca2982fba4dd67457f42bd6b9 /tests | |
parent | df3a1761af2f20d59ae09a7adaa2f5b959047687 (diff) | |
parent | c0905957be0d7db90c9d9f05069a259575753dfe (diff) |
Merge remote-tracking branch 'qt/5.13' into dev
Change-Id: I3dc204fcaa71c01a80b0c622443012eb07964431
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/client/datadevicev1/tst_datadevicev1.cpp | 90 | ||||
-rw-r--r-- | tests/auto/client/seatv4/seatv4.pro | 5 | ||||
-rw-r--r-- | tests/auto/client/seatv4/tst_seatv4.cpp | 292 | ||||
-rw-r--r-- | tests/auto/client/shared/coreprotocol.cpp | 26 | ||||
-rw-r--r-- | tests/auto/client/shared/coreprotocol.h | 6 | ||||
-rw-r--r-- | tests/auto/client/shared/datadevice.cpp | 54 | ||||
-rw-r--r-- | tests/auto/client/shared/datadevice.h | 36 | ||||
-rw-r--r-- | tests/auto/client/shared/mockcompositor.h | 1 | ||||
-rw-r--r-- | tests/auto/client/shared/xdgshell.cpp | 1 | ||||
-rw-r--r-- | tests/auto/client/shared/xdgshell.h | 5 | ||||
-rw-r--r-- | tests/auto/client/shared_old/mockcompositor.cpp | 24 | ||||
-rw-r--r-- | tests/auto/client/shared_old/mockcompositor.h | 5 | ||||
-rw-r--r-- | tests/auto/client/xdgshell/tst_xdgshell.cpp | 89 |
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); |