diff --git a/src/plugins/platforms/wayland/qwaylanddataoffer.cpp b/src/plugins/platforms/wayland/qwaylanddataoffer.cpp index e31e1220f46..4c06277fe87 100644 --- a/src/plugins/platforms/wayland/qwaylanddataoffer.cpp +++ b/src/plugins/platforms/wayland/qwaylanddataoffer.cpp @@ -141,7 +141,7 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::T } int pipefd[2]; - if (qt_safe_pipe(pipefd, O_NONBLOCK) == -1) { + if (qt_safe_pipe(pipefd) == -1) { qWarning("QWaylandMimeData: pipe2() failed"); return QVariant(); } @@ -163,23 +163,32 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::T int QWaylandMimeData::readData(int fd, QByteArray &data) const { - char buf[4096]; - int retryCount = 0; - int n; - while (true) { - n = QT_READ(fd, buf, sizeof buf); - if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) && ++retryCount < 1000) - usleep(1000); - else - break; - } - if (retryCount >= 1000) + fd_set readset; + FD_ZERO(&readset); + FD_SET(fd, &readset); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + int ready = select(FD_SETSIZE, &readset, nullptr, nullptr, &timeout); + if (ready < 0) { + qWarning() << "QWaylandDataOffer: select() failed"; + return -1; + } else if (ready == 0) { qWarning("QWaylandDataOffer: timeout reading from pipe"); - if (n > 0) { - data.append(buf, n); - n = readData(fd, data); + return -1; + } else { + char buf[4096]; + int n = QT_READ(fd, buf, sizeof buf); + + if (n > 0) { + data.append(buf, n); + n = readData(fd, data); + } else if (n < 0) { + qWarning("QWaylandDataOffer: read() failed"); + } + return n; } - return n; } } diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index cb4f190ad86..a17e8917a66 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -189,9 +189,9 @@ void QWaylandDisplay::checkError() const int ecode = wl_display_get_error(mDisplay); if ((ecode == EPIPE || ecode == ECONNRESET)) { // special case this to provide a nicer error - qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + qFatal("The Wayland connection broke. Did the Wayland compositor die?"); } else { - qErrnoWarning(ecode, "The Wayland connection experienced a fatal error"); + qFatal("The Wayland connection experienced a fatal error: %s", strerror(ecode)); } } @@ -201,25 +201,16 @@ void QWaylandDisplay::flushRequests() wl_display_read_events(mDisplay); } - if (wl_display_dispatch_pending(mDisplay) < 0) { + if (wl_display_dispatch_pending(mDisplay) < 0) checkError(); - exitWithError(); - } wl_display_flush(mDisplay); } void QWaylandDisplay::blockingReadEvents() { - if (wl_display_dispatch(mDisplay) < 0) { + if (wl_display_dispatch(mDisplay) < 0) checkError(); - exitWithError(); - } -} - -void QWaylandDisplay::exitWithError() -{ - ::exit(1); } wl_event_queue *QWaylandDisplay::createEventQueue() @@ -248,10 +239,9 @@ void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::functionsurface->handleMouse(mParent, *mFrameData.event); + if (auto *event = mFrameData.event) { + if (auto window = event->surface) { + window->handleMouse(mParent, *event); + } else if (mFrameData.event->type == QWaylandPointerEvent::Type::Release) { + // If the window has been destroyed, we still need to report an up event, but it can't + // be handled by the destroyed window (obviously), so send the event here instead. + QWindowSystemInterface::handleMouseEvent(nullptr, event->timestamp, event->local, + event->global, event->buttons, event->modifiers); + } delete mFrameData.event; mFrameData.event = nullptr; } diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h index 06ba5d5664e..7fbb5667f70 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h @@ -148,6 +148,10 @@ public: virtual Pointer *createPointer(QWaylandInputDevice *device); virtual Touch *createTouch(QWaylandInputDevice *device); + Keyboard *keyboard() const; + Pointer *pointer() const; + Touch *touch() const; + private: QWaylandDisplay *mQDisplay = nullptr; struct wl_display *mDisplay = nullptr; @@ -248,6 +252,8 @@ public: Qt::KeyboardModifiers modifiers() const; + struct ::wl_keyboard *wl_keyboard() { return QtWayland::wl_keyboard::object(); } + private slots: void handleFocusDestroyed(); void handleFocusLost(); @@ -284,6 +290,8 @@ public: #endif QWaylandInputDevice *seat() const { return mParent; } + struct ::wl_pointer *wl_pointer() { return QtWayland::wl_pointer::object(); } + protected: void pointer_enter(uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) override; @@ -377,6 +385,8 @@ public: bool allTouchPointsReleased(); void releasePoints(); + struct ::wl_touch *wl_touch() { return QtWayland::wl_touch::object(); } + QWaylandInputDevice *mParent = nullptr; QPointer mFocus; QList mTouchPoints; @@ -436,7 +446,7 @@ public: QPoint pixelDelta; QPoint angleDelta; Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; - QWaylandWindow *surface = nullptr; + QPointer surface; }; } diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index 7f79d6b9863..f6a80e18fde 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -310,11 +310,14 @@ QPlatformTheme *QWaylandIntegration::createPlatformTheme(const QString &name) co return GenericWaylandTheme::createUnixTheme(name); } +// May be called from non-GUI threads QWaylandClientBufferIntegration *QWaylandIntegration::clientBufferIntegration() const { - if (!mClientBufferIntegrationInitialized) + // Do an inexpensive check first to avoid locking whenever possible + if (Q_UNLIKELY(!mClientBufferIntegrationInitialized)) const_cast(this)->initializeClientBufferIntegration(); + Q_ASSERT(mClientBufferIntegrationInitialized); return mClientBufferIntegration && mClientBufferIntegration->isValid() ? mClientBufferIntegration.data() : nullptr; } @@ -334,9 +337,12 @@ QWaylandShellIntegration *QWaylandIntegration::shellIntegration() const return mShellIntegration.data(); } +// May be called from non-GUI threads void QWaylandIntegration::initializeClientBufferIntegration() { - mClientBufferIntegrationInitialized = true; + QMutexLocker lock(&mClientBufferInitLock); + if (mClientBufferIntegrationInitialized) + return; QString targetKey = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_CLIENT_BUFFER_INTEGRATION")); @@ -352,22 +358,25 @@ void QWaylandIntegration::initializeClientBufferIntegration() if (targetKey.isEmpty()) { qWarning("Failed to determine what client buffer integration to use"); - return; - } - - QStringList keys = QWaylandClientBufferIntegrationFactory::keys(); - qCDebug(lcQpaWayland) << "Available client buffer integrations:" << keys; - - if (keys.contains(targetKey)) - mClientBufferIntegration.reset(QWaylandClientBufferIntegrationFactory::create(targetKey, QStringList())); - - if (mClientBufferIntegration) { - qCDebug(lcQpaWayland) << "Initializing client buffer integration" << targetKey; - mClientBufferIntegration->initialize(mDisplay.data()); } else { - qCWarning(lcQpaWayland) << "Failed to load client buffer integration:" << targetKey; - qCWarning(lcQpaWayland) << "Available client buffer integrations:" << keys; + QStringList keys = QWaylandClientBufferIntegrationFactory::keys(); + qCDebug(lcQpaWayland) << "Available client buffer integrations:" << keys; + + if (keys.contains(targetKey)) + mClientBufferIntegration.reset(QWaylandClientBufferIntegrationFactory::create(targetKey, QStringList())); + + if (mClientBufferIntegration) { + qCDebug(lcQpaWayland) << "Initializing client buffer integration" << targetKey; + mClientBufferIntegration->initialize(mDisplay.data()); + } else { + qCWarning(lcQpaWayland) << "Failed to load client buffer integration:" << targetKey; + qCWarning(lcQpaWayland) << "Available client buffer integrations:" << keys; + } } + + // This must be set last to make sure other threads don't use the + // integration before initialization is complete. + mClientBufferIntegrationInitialized = true; } void QWaylandIntegration::initializeServerBufferIntegration() diff --git a/src/plugins/platforms/wayland/qwaylandintegration_p.h b/src/plugins/platforms/wayland/qwaylandintegration_p.h index 3aef2c4d988..a66999c7f22 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration_p.h +++ b/src/plugins/platforms/wayland/qwaylandintegration_p.h @@ -54,6 +54,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -152,6 +153,7 @@ private: mutable QScopedPointer mAccessibility; #endif bool mFailed = false; + QMutex mClientBufferInitLock; bool mClientBufferIntegrationInitialized = false; bool mServerBufferIntegrationInitialized = false; bool mShellIntegrationInitialized = false; diff --git a/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp b/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp index cf227d489a5..b4ecc009098 100644 --- a/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp +++ b/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp @@ -47,6 +47,7 @@ #include "qwaylanddisplay_p.h" #include "qwaylandwindowmanagerintegration_p.h" #include "qwaylandscreen_p.h" +#include "qwaylandinputdevice_p.h" #include #include #include @@ -76,6 +77,27 @@ void *QWaylandNativeInterface::nativeResourceForIntegration(const QByteArray &re if (lowerCaseResource == "egldisplay" && m_integration->clientBufferIntegration()) return m_integration->clientBufferIntegration()->nativeResource(QWaylandClientBufferIntegration::EglDisplay); + if (lowerCaseResource == "wl_seat") + return m_integration->display()->defaultInputDevice()->wl_seat(); + if (lowerCaseResource == "wl_keyboard") { + auto *keyboard = m_integration->display()->defaultInputDevice()->keyboard(); + if (keyboard) + return keyboard->wl_keyboard(); + return nullptr; + } + if (lowerCaseResource == "wl_pointer") { + auto *pointer = m_integration->display()->defaultInputDevice()->pointer(); + if (pointer) + return pointer->wl_pointer(); + return nullptr; + } + if (lowerCaseResource == "wl_touch") { + auto *touch = m_integration->display()->defaultInputDevice()->touch(); + if (touch) + return touch->wl_touch(); + return nullptr; + } + return nullptr; } diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index ce58003cc67..60317cda725 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -205,6 +205,7 @@ void QWaylandWindow::initializeWlSurface() connect(mSurface.data(), &QWaylandSurface::screensChanged, this, &QWaylandWindow::handleScreensChanged); mSurface->m_window = this; + emit wlSurfaceCreated(); } bool QWaylandWindow::shouldCreateShellSurface() const diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index 8d8565d7761..471ace4cdb3 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -203,6 +203,7 @@ public slots: void applyConfigure(); signals: + void wlSurfaceCreated(); void wlSurfaceDestroyed(); protected: diff --git a/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h b/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h index 85905c02fa3..72cc8401c26 100644 --- a/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h +++ b/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h @@ -51,7 +51,26 @@ class QWaylandSharedMemoryFormatHelper { public: static inline wl_shm_format fromQImageFormat(QImage::Format format); - static inline QImage::Format fromWaylandShmFormat(wl_shm_format format); + static inline QImage::Format fromWaylandShmFormat(wl_shm_format format) + { + switch (format) { + case WL_SHM_FORMAT_XRGB8888: return QImage::Format_RGB32; + case WL_SHM_FORMAT_ARGB8888: return QImage::Format_ARGB32_Premultiplied; + case WL_SHM_FORMAT_RGB565: return QImage::Format_RGB16; + case WL_SHM_FORMAT_XRGB1555: return QImage::Format_RGB555; + case WL_SHM_FORMAT_RGB888: return QImage::Format_RGB888; + case WL_SHM_FORMAT_XRGB4444: return QImage::Format_RGB444; + case WL_SHM_FORMAT_ARGB4444: return QImage::Format_ARGB4444_Premultiplied; + case WL_SHM_FORMAT_XBGR8888: return QImage::Format_RGBX8888; + case WL_SHM_FORMAT_ABGR8888: return QImage::Format_RGBA8888_Premultiplied; + case WL_SHM_FORMAT_XBGR2101010: return QImage::Format_BGR30; + case WL_SHM_FORMAT_ABGR2101010: return QImage::Format_A2BGR30_Premultiplied; + case WL_SHM_FORMAT_XRGB2101010: return QImage::Format_RGB30; + case WL_SHM_FORMAT_ARGB2101010: return QImage::Format_A2RGB30_Premultiplied; + case WL_SHM_FORMAT_C8: return QImage::Format_Alpha8; + default: return QImage::Format_Invalid; + } + } static inline QVector supportedWaylandFormats(); private: @@ -108,16 +127,6 @@ wl_shm_format QWaylandSharedMemoryFormatHelper::fromQImageFormat(QImage::Format return array.data[format]; } -QImage::Format QWaylandSharedMemoryFormatHelper::fromWaylandShmFormat(wl_shm_format format) -{ - Array array = getData(); - for (size_t i = 0; i < array.size; i++) { - if (array.data[i] == format) - return QImage::Format(i); - } - return QImage::Format_Invalid; -} - QVector QWaylandSharedMemoryFormatHelper::supportedWaylandFormats() { QVector retFormats; diff --git a/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp b/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp index fe68d520d59..35ac7252866 100644 --- a/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp +++ b/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp @@ -66,7 +66,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 +104,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 +142,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: 😂"); } diff --git a/tests/auto/wayland/output/tst_output.cpp b/tests/auto/wayland/output/tst_output.cpp index 2d2c8efd682..29c773cf65e 100644 --- a/tests/auto/wayland/output/tst_output.cpp +++ b/tests/auto/wayland/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/wayland/primaryselectionv1/tst_primaryselectionv1.cpp b/tests/auto/wayland/primaryselectionv1/tst_primaryselectionv1.cpp index 281e4c5d11a..216db85cd6b 100644 --- a/tests/auto/wayland/primaryselectionv1/tst_primaryselectionv1.cpp +++ b/tests/auto/wayland/primaryselectionv1/tst_primaryselectionv1.cpp @@ -268,7 +268,7 @@ void tst_primaryselectionv1::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()); } @@ -329,8 +329,11 @@ void tst_primaryselectionv1::pasteAscii() 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"); @@ -372,8 +375,11 @@ void tst_primaryselectionv1::pasteUtf8() 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: 😂"); @@ -428,8 +434,11 @@ void tst_primaryselectionv1::copy() 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)); diff --git a/tests/auto/wayland/seatv4/tst_seatv4.cpp b/tests/auto/wayland/seatv4/tst_seatv4.cpp index 77304deafb3..40f8742a2fb 100644 --- a/tests/auto/wayland/seatv4/tst_seatv4.cpp +++ b/tests/auto/wayland/seatv4/tst_seatv4.cpp @@ -212,22 +212,20 @@ void tst_seatv4::simpleAxis_data() { QTest::addColumn("axis"); QTest::addColumn("value"); - QTest::addColumn("orientation"); QTest::addColumn("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 { @@ -256,27 +254,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 m_events; }; @@ -299,7 +288,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() diff --git a/tests/auto/wayland/seatv5/tst_seatv5.cpp b/tests/auto/wayland/seatv5/tst_seatv5.cpp index 5b9235d9aa3..ca8de31acb1 100644 --- a/tests/auto/wayland/seatv5/tst_seatv5.cpp +++ b/tests/auto/wayland/seatv5/tst_seatv5.cpp @@ -41,18 +41,11 @@ public: removeAll(); - uint capabilities = MockCompositor::Seat::capability_pointer; + uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; int version = 5; add(capabilities, version); }); } - - Pointer *pointer() - { - auto *seat = get(); - Q_ASSERT(seat); - return seat->m_pointer; - } }; class tst_seatv5 : public QObject, private SeatV5Compositor @@ -61,6 +54,8 @@ class tst_seatv5 : public QObject, private SeatV5Compositor private slots: void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } void bindsToSeat(); + + // Pointer tests void createsPointer(); void setsCursorOnEnter(); void usesEnterSerial(); @@ -69,6 +64,10 @@ private slots: void fingerScroll(); void fingerScrollSlow(); void wheelDiscreteScroll(); + + // Touch tests + void createsTouch(); + void singleTap(); }; void tst_seatv5::bindsToSeat() @@ -128,12 +127,8 @@ public: QRasterWindow::wheelEvent(event); // qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta(); - if (event->phase() == Qt::ScrollUpdate || event->phase() == Qt::NoScrollPhase) { - // Angle delta should always be provided (says docs, but QPA sends compatibility events - // for Qt4 with zero angleDelta, and with a delta) - QVERIFY(!event->angleDelta().isNull() || event->delta()); - } else { - // Shouldn't have deltas in the other phases + 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)); } @@ -145,13 +140,6 @@ public: // We didn't press any buttons QCOMPARE(event->buttons(), Qt::NoButton); - if (!event->angleDelta().isNull()) { - if (event->orientation() == Qt::Horizontal) - QCOMPARE(event->delta(), event->angleDelta().x()); - else - QCOMPARE(event->delta(), event->angleDelta().y()); - } - m_events.append(Event{event}); } struct Event // Because I didn't find a convenient way to copy it entirely @@ -161,14 +149,12 @@ public: : phase(event->phase()) , pixelDelta(event->pixelDelta()) , angleDelta(event->angleDelta()) - , orientation(event->orientation()) , source(event->source()) { } const Qt::ScrollPhase phase{}; const QPoint pixelDelta; const QPoint angleDelta; // eights of a degree, positive is upwards, left - const Qt::Orientation orientation{}; const Qt::MouseEventSource source{}; }; QVector m_events; @@ -178,22 +164,20 @@ void tst_seatv5::simpleAxis_data() { QTest::addColumn("axis"); QTest::addColumn("value"); - QTest::addColumn("orientation"); QTest::addColumn("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_seatv5::simpleAxis() { QFETCH(uint, axis); QFETCH(qreal, value); - QFETCH(Qt::Orientation, orientation); QFETCH(QPoint, angleDelta); WheelWindow window; @@ -220,7 +204,6 @@ void tst_seatv5::simpleAxis() // 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.orientation, orientation); QCOMPARE(e.angleDelta, angleDelta); } @@ -263,7 +246,7 @@ void tst_seatv5::fingerScroll() { auto e = window.m_events.takeFirst(); QCOMPARE(e.phase, Qt::ScrollUpdate); - QCOMPARE(e.orientation, Qt::Vertical); + 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 @@ -281,7 +264,7 @@ void tst_seatv5::fingerScroll() { auto e = window.m_events.takeFirst(); QCOMPARE(e.phase, Qt::ScrollUpdate); - QCOMPARE(e.orientation, Qt::Horizontal); + 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 } @@ -373,7 +356,7 @@ void tst_seatv5::wheelDiscreteScroll() { auto e = window.m_events.takeFirst(); QCOMPARE(e.phase, Qt::NoScrollPhase); - QCOMPARE(e.orientation, Qt::Vertical); + 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. @@ -383,5 +366,70 @@ void tst_seatv5::wheelDiscreteScroll() } } +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 touchPoints; + }; + QVector 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())); + } +} + QCOMPOSITOR_TEST_MAIN(tst_seatv5) #include "tst_seatv5.moc" diff --git a/tests/auto/wayland/shared/coreprotocol.cpp b/tests/auto/wayland/shared/coreprotocol.cpp index f9335e7834a..120c256c653 100644 --- a/tests/auto/wayland/shared/coreprotocol.cpp +++ b/tests/auto/wayland/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"; @@ -371,6 +397,40 @@ 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::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(); diff --git a/tests/auto/wayland/shared/coreprotocol.h b/tests/auto/wayland/shared/coreprotocol.h index 264c5f694e6..50812c29658 100644 --- a/tests/auto/wayland/shared/coreprotocol.h +++ b/tests/auto/wayland/shared/coreprotocol.h @@ -38,6 +38,7 @@ namespace MockCompositor { class WlCompositor; class Output; class Pointer; +class Touch; class Keyboard; class CursorRole; class ShmPool; @@ -236,7 +237,7 @@ class Seat : public Global, public QtWaylandServer::wl_seat { Q_OBJECT public: - explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard, 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 @@ -247,6 +248,9 @@ public: Pointer* m_pointer = nullptr; QVector m_oldPointers; + Touch* m_touch = nullptr; + QVector m_oldTouchs; + Keyboard* m_keyboard = nullptr; QVector m_oldKeyboards; @@ -259,8 +263,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; }; @@ -307,6 +311,18 @@ 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 sendFrame(wl_client *client); + + Seat *m_seat = nullptr; +}; + class Keyboard : public QObject, public QtWaylandServer::wl_keyboard { Q_OBJECT diff --git a/tests/auto/wayland/shared/mockcompositor.cpp b/tests/auto/wayland/shared/mockcompositor.cpp index 6b9af4295b8..5f2d89078e5 100644 --- a/tests/auto/wayland/shared/mockcompositor.cpp +++ b/tests/auto/wayland/shared/mockcompositor.cpp @@ -41,7 +41,7 @@ DefaultCompositor::DefaultCompositor() add(); auto *output = add(); output->m_data.physicalSize = output->m_data.mode.physicalSizeForDpi(96); - add(Seat::capability_pointer | Seat::capability_keyboard); + add(Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch); add(); add(); // TODO: other shells, viewporter, xdgoutput etc diff --git a/tests/auto/wayland/shared/mockcompositor.h b/tests/auto/wayland/shared/mockcompositor.h index aa85a4aeace..fc4d7cc4640 100644 --- a/tests/auto/wayland/shared/mockcompositor.h +++ b/tests/auto/wayland/shared/mockcompositor.h @@ -60,6 +60,7 @@ public: XdgToplevel *xdgToplevel(int i = 0) { return get()->toplevel(i); } XdgPopup *xdgPopup(int i = 0) { return get()->popup(i); } Pointer *pointer() { auto *seat = get(); Q_ASSERT(seat); return seat->m_pointer; } + Touch *touch() { auto *seat = get(); Q_ASSERT(seat); return seat->m_touch; } Surface *cursorSurface() { auto *p = pointer(); return p ? p->cursorSurface() : nullptr; } Keyboard *keyboard() { auto *seat = get(); Q_ASSERT(seat); return seat->m_keyboard; } uint sendXdgShellPing(); @@ -84,6 +85,7 @@ public: int main(int argc, char **argv) \ { \ setenv("XDG_RUNTIME_DIR", ".", 1); \ + setenv("XDG_CURRENT_DESKTOP", "qtwaylandtests", 1); \ setenv("QT_QPA_PLATFORM", "wayland", 1); \ test tc; \ QGuiApplication app(argc, argv); \ diff --git a/tests/auto/wayland/xdgshell/tst_xdgshell.cpp b/tests/auto/wayland/xdgshell/tst_xdgshell.cpp index c887e5d4442..ac5c24988f8 100644 --- a/tests/auto/wayland/xdgshell/tst_xdgshell.cpp +++ b/tests/auto/wayland/xdgshell/tst_xdgshell.cpp @@ -214,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); @@ -298,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()); @@ -315,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)); @@ -380,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()); @@ -399,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