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 m_current_selection_source = nullptr;
65 }
66}
67
68void DataDeviceManager::retain()
69{
70 QList<QString> offers = m_current_selection_source->mimeTypes();
71 finishReadFromClient();
72 if (m_retainedReadIndex >= offers.size()) {
73 QWaylandCompositorPrivate::get(compositor: m_compositor)->feedRetainedSelectionData(data: &m_retainedData);
74 return;
75 }
76 QString mimeType = offers.at(i: m_retainedReadIndex);
77 m_retainedReadBuf.clear();
78 int fd[2];
79 if (pipe(pipedes: fd) == -1) {
80 qWarning(msg: "Clipboard: Failed to create pipe");
81 return;
82 }
83 fcntl(fd: fd[0], F_SETFL, fcntl(fd: fd[0], F_GETFL, 0) | O_NONBLOCK);
84 m_current_selection_source->send(mimeType, fd: fd[1]);
85 m_retainedReadNotifier = new QSocketNotifier(fd[0], QSocketNotifier::Read, this);
86 connect(sender: m_retainedReadNotifier, signal: &QSocketNotifier::activated, context: this, slot: &DataDeviceManager::readFromClient);
87}
88
89void DataDeviceManager::finishReadFromClient(bool exhausted)
90{
91 Q_UNUSED(exhausted);
92 if (m_retainedReadNotifier) {
93 if (exhausted) {
94 int fd = m_retainedReadNotifier->socket();
95 delete m_retainedReadNotifier;
96 close(fd: fd);
97 } else {
98 // Do not close the handle or destroy the read notifier here
99 // or else clients may SIGPIPE.
100 m_obsoleteRetainedReadNotifiers.append(t: m_retainedReadNotifier);
101 }
102 m_retainedReadNotifier = nullptr;
103 }
104}
105
106void DataDeviceManager::readFromClient(int fd)
107{
108 static char buf[4096];
109 int obsCount = m_obsoleteRetainedReadNotifiers.size();
110 for (int i = 0; i < obsCount; ++i) {
111 QSocketNotifier *sn = m_obsoleteRetainedReadNotifiers.at(i);
112 if (sn->socket() == fd) {
113 // Read and drop the data, stopping to read and closing the handle
114 // is not yet safe because that could kill the client with SIGPIPE
115 // when it still tries to write.
116 int n;
117 do {
118 n = QT_READ(fd, data: buf, maxlen: sizeof buf);
119 } while (n > 0);
120 if (n != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
121 m_obsoleteRetainedReadNotifiers.removeAt(i);
122 delete sn;
123 close(fd: fd);
124 }
125 return;
126 }
127 }
128 int n = QT_READ(fd, data: buf, maxlen: sizeof buf);
129 if (n <= 0) {
130 if (n != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
131 finishReadFromClient(exhausted: true);
132 QList<QString> offers = m_current_selection_source->mimeTypes();
133 QString mimeType = offers.at(i: m_retainedReadIndex);
134 m_retainedData.setData(mimetype: mimeType, data: m_retainedReadBuf);
135 ++m_retainedReadIndex;
136 retain();
137 }
138 } else {
139 m_retainedReadBuf.append(s: buf, len: n);
140 }
141}
142
143DataSource *DataDeviceManager::currentSelectionSource()
144{
145 return m_current_selection_source;
146}
147
148struct wl_display *DataDeviceManager::display() const
149{
150 return m_compositor->display();
151}
152
153void DataDeviceManager::overrideSelection(const QMimeData &mimeData)
154{
155 const QStringList formats = mimeData.formats();
156 if (formats.isEmpty())
157 return;
158
159 m_retainedData.clear();
160 for (const QString &format : formats)
161 m_retainedData.setData(mimetype: format, data: mimeData.data(mimetype: format));
162
163 QWaylandCompositorPrivate::get(compositor: m_compositor)->feedRetainedSelectionData(data: &m_retainedData);
164
165 m_compositorOwnsSelection = true;
166
167 if (m_current_selection_source) {
168 finishReadFromClient();
169 m_current_selection_source->cancel();
170 // wl_data_source::cancelled will destroy it and
171 // it will make m_current_selection_source as nullptr.
172 // But it will immediately affect the selection here.
173 m_current_selection_source = nullptr;
174 }
175
176 QWaylandSeat *dev = m_compositor->defaultSeat();
177 QWaylandSurface *focusSurface = dev->keyboardFocus();
178 if (focusSurface)
179 offerFromCompositorToClient(
180 clientDataDeviceResource: QWaylandSeatPrivate::get(device: dev)->dataDevice()->resourceMap().value(focusSurface->waylandClient())->handle);
181}
182
183bool DataDeviceManager::offerFromCompositorToClient(wl_resource *clientDataDeviceResource)
184{
185 if (!m_compositorOwnsSelection)
186 return false;
187
188 wl_client *client = wl_resource_get_client(clientDataDeviceResource);
189 //qDebug("compositor offers %d types to %p", int(m_retainedData.formats().count()), client);
190
191 struct wl_resource *selectionOffer =
192 wl_resource_create(client, &wl_data_offer_interface, -1, 0);
193 wl_resource_set_implementation(selectionOffer, &compositor_offer_interface, this, nullptr);
194 wl_data_device_send_data_offer(clientDataDeviceResource, selectionOffer);
195 const auto formats = m_retainedData.formats();
196 for (const QString &format : formats) {
197 QByteArray ba = format.toLatin1();
198 wl_data_offer_send_offer(selectionOffer, ba.constData());
199 }
200 wl_data_device_send_selection(clientDataDeviceResource, selectionOffer);
201
202 return true;
203}
204
205void DataDeviceManager::offerRetainedSelection(wl_resource *clientDataDeviceResource)
206{
207 if (m_retainedData.formats().isEmpty())
208 return;
209
210 m_compositorOwnsSelection = true;
211 offerFromCompositorToClient(clientDataDeviceResource);
212}
213
214void DataDeviceManager::data_device_manager_create_data_source(Resource *resource, uint32_t id)
215{
216 new DataSource(resource->client(), id, m_compositor->currentTimeMsecs());
217}
218
219void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat)
220{
221 QWaylandSeat *input_device = QWaylandSeat::fromSeatResource(resource: seat);
222 QWaylandSeatPrivate::get(device: input_device)->clientRequestedDataDevice(dndSelection: this, client: resource->client(), id);
223}
224
225void DataDeviceManager::comp_accept(wl_client *, wl_resource *, uint32_t, const char *)
226{
227}
228
229void DataDeviceManager::comp_receive(wl_client *client, wl_resource *resource, const char *mime_type, int32_t fd)
230{
231 Q_UNUSED(client);
232 DataDeviceManager *self = static_cast<DataDeviceManager *>(wl_resource_get_user_data(resource));
233 //qDebug("client %p wants data for type %s from compositor", client, mime_type);
234 QByteArray content = QWaylandMimeHelper::getByteArray(mimeData: &self->m_retainedData, mimeType: QString::fromLatin1(ba: mime_type));
235 if (!content.isEmpty()) {
236 QFile f;
237 if (f.open(fd, ioFlags: QIODevice::WriteOnly))
238 f.write(data: content);
239 }
240 close(fd: fd);
241}
242
243void DataDeviceManager::comp_destroy(wl_client *, wl_resource *)
244{
245}
246
247QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers")
248QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers")
249
250const struct wl_data_offer_interface DataDeviceManager::compositor_offer_interface = {
251 DataDeviceManager::comp_accept,
252 DataDeviceManager::comp_receive,
253 DataDeviceManager::comp_destroy
254};
255
256} //namespace
257
258QT_END_NAMESPACE
259
260#include "moc_qwldatadevicemanager_p.cpp"
261

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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