1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qwldatadevicemanager_p.h"
5
6#include <QtWaylandCompositor/QWaylandCompositor>
7
8#include <QtWaylandCompositor/private/qwaylandcompositor_p.h>
9#include <QtWaylandCompositor/private/qwaylandseat_p.h>
10#include "qwldatadevice_p.h"
11#include "qwldatasource_p.h"
12#include "qwldataoffer_p.h"
13#include "qwaylandmimehelper_p.h"
14
15#include <QtCore/QDebug>
16#include <QtCore/QSocketNotifier>
17#include <fcntl.h>
18#include <QtCore/private/qcore_unix_p.h>
19#include <QtCore/QFile>
20
21QT_BEGIN_NAMESPACE
22
23namespace QtWayland {
24
25DataDeviceManager::DataDeviceManager(QWaylandCompositor *compositor)
26 : wl_data_device_manager(compositor->display(), 1)
27 , m_compositor(compositor)
28{
29}
30
31void DataDeviceManager::setCurrentSelectionSource(DataSource *source)
32{
33 if (m_current_selection_source && source
34 && m_current_selection_source->time() > source->time()) {
35 qDebug() << "Trying to set older selection";
36 return;
37 }
38
39 m_compositorOwnsSelection = false;
40
41 finishReadFromClient();
42
43 m_current_selection_source = source;
44 if (source)
45 source->setManager(this);
46
47 // When retained selection is enabled, the compositor will query all the data from the client.
48 // This makes it possible to
49 // 1. supply the selection after the offering client is gone
50 // 2. make it possible for the compositor to participate in copy-paste
51 // The downside is decreased performance, therefore this mode has to be enabled
52 // explicitly in the compositors.
53 if (source && m_compositor->retainedSelectionEnabled()) {
54 m_retainedData.clear();
55 m_retainedReadIndex = 0;
56 retain();
57 }
58}
59
60void DataDeviceManager::sourceDestroyed(DataSource *source)
61{
62 if (m_current_selection_source == source)
63 finishReadFromClient();
64}
65
66void DataDeviceManager::retain()
67{
68 QList<QString> offers = m_current_selection_source->mimeTypes();
69 finishReadFromClient();
70 if (m_retainedReadIndex >= offers.size()) {
71 QWaylandCompositorPrivate::get(compositor: m_compositor)->feedRetainedSelectionData(data: &m_retainedData);
72 return;
73 }
74 QString mimeType = offers.at(i: m_retainedReadIndex);
75 m_retainedReadBuf.clear();
76 int fd[2];
77 if (pipe(pipedes: fd) == -1) {
78 qWarning(msg: "Clipboard: Failed to create pipe");
79 return;
80 }
81 fcntl(fd: fd[0], F_SETFL, fcntl(fd: fd[0], F_GETFL, 0) | O_NONBLOCK);
82 m_current_selection_source->send(mimeType, fd: fd[1]);
83 m_retainedReadNotifier = new QSocketNotifier(fd[0], QSocketNotifier::Read, this);
84 connect(sender: m_retainedReadNotifier, signal: &QSocketNotifier::activated, context: this, slot: &DataDeviceManager::readFromClient);
85}
86
87void DataDeviceManager::finishReadFromClient(bool exhausted)
88{
89 Q_UNUSED(exhausted);
90 if (m_retainedReadNotifier) {
91 if (exhausted) {
92 int fd = m_retainedReadNotifier->socket();
93 delete m_retainedReadNotifier;
94 close(fd: fd);
95 } else {
96 // Do not close the handle or destroy the read notifier here
97 // or else clients may SIGPIPE.
98 m_obsoleteRetainedReadNotifiers.append(t: m_retainedReadNotifier);
99 }
100 m_retainedReadNotifier = nullptr;
101 }
102}
103
104void DataDeviceManager::readFromClient(int fd)
105{
106 static char buf[4096];
107 int obsCount = m_obsoleteRetainedReadNotifiers.size();
108 for (int i = 0; i < obsCount; ++i) {
109 QSocketNotifier *sn = m_obsoleteRetainedReadNotifiers.at(i);
110 if (sn->socket() == fd) {
111 // Read and drop the data, stopping to read and closing the handle
112 // is not yet safe because that could kill the client with SIGPIPE
113 // when it still tries to write.
114 int n;
115 do {
116 n = QT_READ(fd, data: buf, maxlen: sizeof buf);
117 } while (n > 0);
118 if (n != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
119 m_obsoleteRetainedReadNotifiers.removeAt(i);
120 delete sn;
121 close(fd: fd);
122 }
123 return;
124 }
125 }
126 int n = QT_READ(fd, data: buf, maxlen: sizeof buf);
127 if (n <= 0) {
128 if (n != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
129 finishReadFromClient(exhausted: true);
130 QList<QString> offers = m_current_selection_source->mimeTypes();
131 QString mimeType = offers.at(i: m_retainedReadIndex);
132 m_retainedData.setData(mimetype: mimeType, data: m_retainedReadBuf);
133 ++m_retainedReadIndex;
134 retain();
135 }
136 } else {
137 m_retainedReadBuf.append(s: buf, len: n);
138 }
139}
140
141DataSource *DataDeviceManager::currentSelectionSource()
142{
143 return m_current_selection_source;
144}
145
146struct wl_display *DataDeviceManager::display() const
147{
148 return m_compositor->display();
149}
150
151void DataDeviceManager::overrideSelection(const QMimeData &mimeData)
152{
153 const QStringList formats = mimeData.formats();
154 if (formats.isEmpty())
155 return;
156
157 m_retainedData.clear();
158 for (const QString &format : formats)
159 m_retainedData.setData(mimetype: format, data: mimeData.data(mimetype: format));
160
161 QWaylandCompositorPrivate::get(compositor: m_compositor)->feedRetainedSelectionData(data: &m_retainedData);
162
163 m_compositorOwnsSelection = true;
164
165 QWaylandSeat *dev = m_compositor->defaultSeat();
166 QWaylandSurface *focusSurface = dev->keyboardFocus();
167 if (focusSurface)
168 offerFromCompositorToClient(
169 clientDataDeviceResource: QWaylandSeatPrivate::get(device: dev)->dataDevice()->resourceMap().value(focusSurface->waylandClient())->handle);
170}
171
172bool DataDeviceManager::offerFromCompositorToClient(wl_resource *clientDataDeviceResource)
173{
174 if (!m_compositorOwnsSelection)
175 return false;
176
177 wl_client *client = wl_resource_get_client(clientDataDeviceResource);
178 //qDebug("compositor offers %d types to %p", m_retainedData.formats().count(), client);
179
180 struct wl_resource *selectionOffer =
181 wl_resource_create(client, &wl_data_offer_interface, -1, 0);
182 wl_resource_set_implementation(selectionOffer, &compositor_offer_interface, this, nullptr);
183 wl_data_device_send_data_offer(clientDataDeviceResource, selectionOffer);
184 const auto formats = m_retainedData.formats();
185 for (const QString &format : formats) {
186 QByteArray ba = format.toLatin1();
187 wl_data_offer_send_offer(selectionOffer, ba.constData());
188 }
189 wl_data_device_send_selection(clientDataDeviceResource, selectionOffer);
190
191 return true;
192}
193
194void DataDeviceManager::offerRetainedSelection(wl_resource *clientDataDeviceResource)
195{
196 if (m_retainedData.formats().isEmpty())
197 return;
198
199 m_compositorOwnsSelection = true;
200 offerFromCompositorToClient(clientDataDeviceResource);
201}
202
203void DataDeviceManager::data_device_manager_create_data_source(Resource *resource, uint32_t id)
204{
205 new DataSource(resource->client(), id, m_compositor->currentTimeMsecs());
206}
207
208void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat)
209{
210 QWaylandSeat *input_device = QWaylandSeat::fromSeatResource(resource: seat);
211 QWaylandSeatPrivate::get(device: input_device)->clientRequestedDataDevice(dndSelection: this, client: resource->client(), id);
212}
213
214void DataDeviceManager::comp_accept(wl_client *, wl_resource *, uint32_t, const char *)
215{
216}
217
218void DataDeviceManager::comp_receive(wl_client *client, wl_resource *resource, const char *mime_type, int32_t fd)
219{
220 Q_UNUSED(client);
221 DataDeviceManager *self = static_cast<DataDeviceManager *>(wl_resource_get_user_data(resource));
222 //qDebug("client %p wants data for type %s from compositor", client, mime_type);
223 QByteArray content = QWaylandMimeHelper::getByteArray(mimeData: &self->m_retainedData, mimeType: QString::fromLatin1(ba: mime_type));
224 if (!content.isEmpty()) {
225 QFile f;
226 if (f.open(fd, ioFlags: QIODevice::WriteOnly))
227 f.write(data: content);
228 }
229 close(fd: fd);
230}
231
232void DataDeviceManager::comp_destroy(wl_client *, wl_resource *)
233{
234}
235
236QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers")
237QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers")
238
239const struct wl_data_offer_interface DataDeviceManager::compositor_offer_interface = {
240 DataDeviceManager::comp_accept,
241 DataDeviceManager::comp_receive,
242 DataDeviceManager::comp_destroy
243};
244
245} //namespace
246
247QT_END_NAMESPACE
248
249#include "moc_qwldatadevicemanager_p.cpp"
250

source code of qtwayland/src/compositor/wayland_wrapper/qwldatadevicemanager.cpp