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.

Change-Id: Icd4406ff6297ea2800f4e1389ffc2878ee1ccb65
Reviewed-by: David Edmundson <davidedmundson@kde.org>
This commit is contained in:
Kai Uwe Broulik 2023-08-05 16:48:37 +02:00
parent 349cb9eb7d
commit 9c1cd44144
2 changed files with 93 additions and 0 deletions

View File

@ -20,6 +20,47 @@ static QString utf8Text()
return QStringLiteral("text/plain;charset=utf-8"); 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<const char16_t *>(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) QWaylandDataOffer::QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer)
: QtWayland::wl_data_offer(offer) : QtWayland::wl_data_offer(offer)
, m_display(display) , m_display(display)
@ -105,6 +146,9 @@ bool QWaylandMimeData::hasFormat_sys(const QString &mimeType) const
if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text())) if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text()))
return true; return true;
if (mimeType == uriList() && m_types.contains(mozUrl()))
return true;
return false; return false;
} }
@ -126,6 +170,8 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QMetaType t
if (!m_types.contains(mimeType)) { if (!m_types.contains(mimeType)) {
if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text())) if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text()))
mime = utf8Text(); mime = utf8Text();
else if (mimeType == uriList() && m_types.contains(mozUrl()))
mime = mozUrl();
else else
return QVariant(); return QVariant();
} }
@ -147,6 +193,9 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QMetaType t
} }
close(pipefd[0]); close(pipefd[0]);
content = convertData(mimeType, mime, content);
m_data.insert(mimeType, content); m_data.insert(mimeType, content);
return content; return content;
} }

View File

@ -31,6 +31,7 @@ private slots:
void initTestCase(); void initTestCase();
void pasteAscii(); void pasteAscii();
void pasteUtf8(); void pasteUtf8();
void pasteMozUrl();
void destroysPreviousSelection(); void destroysPreviousSelection();
void destroysSelectionWithSurface(); void destroysSelectionWithSurface();
void destroysSelectionOnLeave(); void destroysSelectionOnLeave();
@ -123,6 +124,49 @@ void tst_datadevicev1::pasteUtf8()
QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); 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<QUrl> 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<const char *>(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() void tst_datadevicev1::destroysPreviousSelection()
{ {
QRasterWindow window; QRasterWindow window;