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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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