1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwaylanddataoffer_p.h"
5#include "qwaylanddatadevicemanager_p.h"
6#include "qwaylanddisplay_p.h"
7
8#include <QtCore/private/qcore_unix_p.h>
9#include <QtGui/private/qguiapplication_p.h>
10#include <qpa/qplatformclipboard.h>
11
12#include <QtCore/QDebug>
13
14using namespace std::chrono;
15
16QT_BEGIN_NAMESPACE
17
18namespace QtWaylandClient {
19
20static QString plainText()
21{
22 return QStringLiteral("text/plain");
23}
24
25static QString utf8Text()
26{
27 return QStringLiteral("text/plain;charset=utf-8");
28}
29
30static QString uriList()
31{
32 return QStringLiteral("text/uri-list");
33}
34
35static QString mozUrl()
36{
37 return QStringLiteral("text/x-moz-url");
38}
39
40static QString portalFileTransfer()
41{
42 return QStringLiteral("application/vnd.portal.filetransfer");
43}
44
45static QByteArray convertData(const QString &originalMime, const QString &newMime, const QByteArray &data)
46{
47 if (originalMime == newMime)
48 return data;
49
50 // Convert text/x-moz-url, which is an UTF-16 string of
51 // URL and page title pairs, all separated by line breaks, to text/uri-list.
52 // see also qtbase/src/plugins/platforms/xcb/qxcbmime.cpp
53 if (originalMime == uriList() && newMime == mozUrl()) {
54 if (data.size() > 1) {
55 const quint8 byte0 = data.at(i: 0);
56 const quint8 byte1 = data.at(i: 1);
57
58 if ((byte0 == 0xff && byte1 == 0xfe) || (byte0 == 0xfe && byte1 == 0xff)
59 || (byte0 != 0 && byte1 == 0) || (byte0 == 0 && byte1 != 0)) {
60 QByteArray converted;
61 const QString str = QString::fromUtf16(
62 reinterpret_cast<const char16_t *>(data.constData()), size: data.size() / 2);
63 if (!str.isNull()) {
64 const auto urls = QStringView{str}.split(sep: u'\n');
65 // Only the URL is interesting, skip the page title.
66 for (int i = 0; i < urls.size(); i += 2) {
67 const QUrl url(urls.at(i).trimmed().toString());
68 if (url.isValid()) {
69 converted += url.toEncoded();
70 converted += "\r\n";
71 }
72 }
73 }
74 return converted;
75 // 8 byte encoding, remove a possible 0 at the end.
76 } else {
77 QByteArray converted = data;
78 if (converted.endsWith(c: '\0'))
79 converted.chop(n: 1);
80 converted += "\r\n";
81 return converted;
82 }
83 }
84 }
85
86 return data;
87}
88
89QWaylandDataOffer::QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer)
90 : QtWayland::wl_data_offer(offer)
91 , m_display(display)
92 , m_mimeData(new QWaylandMimeData(this))
93{
94}
95
96QWaylandDataOffer::~QWaylandDataOffer()
97{
98 destroy();
99}
100
101
102QString QWaylandDataOffer::firstFormat() const
103{
104 if (m_mimeData->formats().isEmpty())
105 return QString();
106
107 return m_mimeData->formats().first();
108}
109
110QMimeData *QWaylandDataOffer::mimeData()
111{
112 return m_mimeData.data();
113}
114
115Qt::DropActions QWaylandDataOffer::supportedActions() const
116{
117 if (version() < 3) {
118 return Qt::MoveAction | Qt::CopyAction;
119 }
120
121 return m_supportedActions;
122}
123
124void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd)
125{
126 receive(mimeType, fd);
127 wl_display_flush(m_display->wl_display());
128}
129
130void QWaylandDataOffer::data_offer_offer(const QString &mime_type)
131{
132 m_mimeData->appendFormat(mimeType: mime_type);
133}
134
135void QWaylandDataOffer::data_offer_action(uint32_t dnd_action)
136{
137 Q_UNUSED(dnd_action);
138 // This is the compositor telling the drag target what action it should perform
139 // It does not map nicely into Qt final drop semantics, other than pretending there is only one supported action?
140}
141
142void QWaylandDataOffer::data_offer_source_actions(uint32_t source_actions)
143{
144 m_supportedActions = Qt::DropActions();
145 if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
146 m_supportedActions |= Qt::MoveAction;
147 if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
148 m_supportedActions |= Qt::CopyAction;
149}
150
151QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer)
152 : m_dataOffer(dataOffer)
153{
154}
155
156QWaylandMimeData::~QWaylandMimeData()
157{
158}
159
160void QWaylandMimeData::appendFormat(const QString &mimeType)
161{
162 // "DELETE" is a potential leftover from XdndActionMode sent by e.g. Firefox, ignore it.
163 if (mimeType != QLatin1String("DELETE")) {
164 m_types << mimeType;
165 m_data.remove(key: mimeType); // Clear previous contents
166 }
167}
168
169bool QWaylandMimeData::hasFormat_sys(const QString &mimeType) const
170{
171 return formats().contains(str: mimeType);
172}
173
174QStringList QWaylandMimeData::formats_sys() const
175{
176 QStringList types;
177 types.reserve(asize: m_types.size());
178
179 for (const QString &type : m_types) {
180 QString mime = type;
181
182 if (mime == utf8Text()) {
183 mime = plainText();
184 } else if (mime == mozUrl()) {
185 mime = uriList();
186 }
187
188 if (!types.contains(str: mime)) {
189 types << mime;
190 }
191 }
192
193 return types;
194}
195
196QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QMetaType type) const
197{
198 Q_UNUSED(type);
199
200 auto it = m_data.constFind(key: mimeType);
201 if (it != m_data.constEnd())
202 return *it;
203
204 QString mime = mimeType;
205
206 if (!m_types.contains(str: mimeType)) {
207 if (mimeType == plainText() && m_types.contains(str: utf8Text()))
208 mime = utf8Text();
209 else if (mimeType == uriList() && m_types.contains(str: mozUrl()))
210 mime = mozUrl();
211 else
212 return QVariant();
213 }
214
215 int pipefd[2];
216 if (qt_safe_pipe(pipefd) == -1) {
217 qWarning(msg: "QWaylandMimeData: pipe2() failed");
218 return QVariant();
219 }
220
221 m_dataOffer->startReceiving(mimeType: mime, fd: pipefd[1]);
222
223 close(fd: pipefd[1]);
224
225 QByteArray content;
226 if (readData(fd: pipefd[0], data&: content) != 0) {
227 qWarning(msg: "QWaylandDataOffer: error reading data for mimeType %s", qPrintable(mimeType));
228 content = QByteArray();
229 }
230
231 close(fd: pipefd[0]);
232
233 content = convertData(originalMime: mimeType, newMime: mime, data: content);
234
235 if (mimeType != portalFileTransfer())
236 m_data.insert(key: mimeType, value: content);
237
238 return content;
239}
240
241int QWaylandMimeData::readData(int fd, QByteArray &data) const
242{
243 struct pollfd readset;
244 readset.fd = fd;
245 readset.events = POLLIN;
246
247 Q_FOREVER {
248 int ready = qt_safe_poll(fds: &readset, nfds: 1, deadline: QDeadlineTimer(1s));
249 if (ready < 0) {
250 qWarning() << "QWaylandDataOffer: qt_safe_poll() failed";
251 return -1;
252 } else if (ready == 0) {
253 qWarning(msg: "QWaylandDataOffer: timeout reading from pipe");
254 return -1;
255 } else {
256 char buf[4096];
257 int n = QT_READ(fd, data: buf, maxlen: sizeof buf);
258
259 if (n < 0) {
260 qWarning(msg: "QWaylandDataOffer: read() failed");
261 return -1;
262 } else if (n == 0) {
263 return 0;
264 } else if (n > 0) {
265 data.append(s: buf, len: n);
266 }
267 }
268 }
269}
270
271}
272
273QT_END_NAMESPACE
274

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtwayland/src/client/qwaylanddataoffer.cpp