From 8168df849182aab1424867658254826e8b5ed78e Mon Sep 17 00:00:00 2001 From: Kai Uwe Broulik Date: Sat, 5 Aug 2023 16:48:37 +0200 Subject: client: Convert text/x-moz-urls to text/uri-list Similar to how it's done in the XCB QPA. This format is used by both Firefox and Chrome for exchanging URLs. Pick-to: 6.5 Change-Id: Icd4406ff6297ea2800f4e1389ffc2878ee1ccb65 Reviewed-by: David Edmundson (cherry picked from commit 80a3359598553298b544feae0a7e7399d6cfd9a7) Reviewed-by: Liang Qi --- src/client/qwaylanddataoffer.cpp | 49 ++++++++++++++++++++++ .../auto/client/datadevicev1/tst_datadevicev1.cpp | 44 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/client/qwaylanddataoffer.cpp b/src/client/qwaylanddataoffer.cpp index 046385e91..b22887495 100644 --- a/src/client/qwaylanddataoffer.cpp +++ b/src/client/qwaylanddataoffer.cpp @@ -20,6 +20,47 @@ static QString utf8Text() return QStringLiteral("text/plain;charset=utf-8"); } +static QString uriList() +{ + return QStringLiteral("text/uri-list"); +} + +static QString mozUrl() +{ + return QStringLiteral("text/x-moz-url"); +} + +static QByteArray convertData(const QString &originalMime, const QString &newMime, const QByteArray &data) +{ + if (originalMime == newMime) + return data; + + // Convert text/x-moz-url, which is an UTF-16 string of + // URL and page title pairs, all separated by line breaks, to text/uri-list. + // see also qtbase/src/plugins/platforms/xcb/qxcbmime.cpp + if (originalMime == uriList() && newMime == mozUrl()) { + if (data.size() > 1) { + const QString str = QString::fromUtf16( + reinterpret_cast(data.constData()), data.size() / 2); + if (!str.isNull()) { + QByteArray converted; + const auto urls = QStringView{str}.split(u'\n'); + // Only the URL is interesting, skip the page title. + for (int i = 0; i < urls.size(); i += 2) { + const QUrl url(urls.at(i).trimmed().toString()); + if (url.isValid()) { + converted += url.toEncoded(); + converted += "\r\n"; + } + } + return converted; + } + } + } + + return data; +} + QWaylandDataOffer::QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer) : QtWayland::wl_data_offer(offer) , m_display(display) @@ -105,6 +146,9 @@ bool QWaylandMimeData::hasFormat_sys(const QString &mimeType) const if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text())) return true; + if (mimeType == uriList() && m_types.contains(mozUrl())) + return true; + return false; } @@ -126,6 +170,8 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QMetaType t if (!m_types.contains(mimeType)) { if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text())) mime = utf8Text(); + else if (mimeType == uriList() && m_types.contains(mozUrl())) + mime = mozUrl(); else return QVariant(); } @@ -147,6 +193,9 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QMetaType t } close(pipefd[0]); + + content = convertData(mimeType, mime, content); + m_data.insert(mimeType, content); return content; } diff --git a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp index cdbf7379a..bb831564a 100644 --- a/tests/auto/client/datadevicev1/tst_datadevicev1.cpp +++ b/tests/auto/client/datadevicev1/tst_datadevicev1.cpp @@ -31,6 +31,7 @@ private slots: void initTestCase(); void pasteAscii(); void pasteUtf8(); + void pasteMozUrl(); void destroysPreviousSelection(); void destroysSelectionWithSurface(); void destroysSelectionOnLeave(); @@ -123,6 +124,49 @@ void tst_datadevicev1::pasteUtf8() QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); } +void tst_datadevicev1::pasteMozUrl() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_urls = QGuiApplication::clipboard()->mimeData()->urls(); } + QList m_urls; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/x-moz-url"}); + connect(offer, &DataOffer::receive, [](QString mimeType, int fd) { + QFile file; + file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle); + QCOMPARE(mimeType, "text/x-moz-url"); + const QString content("https://www.qt.io/\nQt\nhttps://www.example.com/\nExample Website"); + // Need UTF-16. + file.write(reinterpret_cast(content.data()), content.size() * 2); + file.close(); + }); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + + QTRY_COMPARE(window.m_urls.count(), 2); + QCOMPARE(window.m_urls.at(0), QUrl("https://www.qt.io/")); + QCOMPARE(window.m_urls.at(1), QUrl("https://www.example.com/")); +} + void tst_datadevicev1::destroysPreviousSelection() { QRasterWindow window; -- cgit v1.2.3