KWin
Loading...
Searching...
No Matches
transfer.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "transfer.h"
10
11#include "databridge.h"
12#include "xwayland.h"
13
14#include "atoms.h"
15#include "wayland/datadevice.h"
16#include "wayland/datasource.h"
17#include "wayland/seat.h"
18#include "wayland_server.h"
19#include "window.h"
20#include "workspace.h"
21
22#include <xcb/xcb_event.h>
23#include <xcb/xfixes.h>
24
25#include <algorithm>
26#include <unistd.h>
27
28#include <xwayland_logging.h>
29
30namespace KWin
31{
32namespace Xwl
33{
34
35// in Bytes: equals 64KB
36static const uint32_t s_incrChunkSize = 63 * 1024;
37
38Transfer::Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject *parent)
39 : QObject(parent)
40 , m_atom(selection)
41 , m_fd(fd)
42 , m_timestamp(timestamp)
43{
44}
45
46void Transfer::createSocketNotifier(QSocketNotifier::Type type)
47{
48 delete m_notifier;
49 m_notifier = new QSocketNotifier(m_fd, type, this);
50}
51
53{
54 delete m_notifier;
55 m_notifier = nullptr;
56}
57
59{
60 if (m_timeout) {
62 }
63 m_timeout = true;
64}
65
67{
69 closeFd();
70 Q_EMIT finished();
71}
72
73void Transfer::closeFd()
74{
75 if (m_fd < 0) {
76 return;
77 }
78 close(m_fd);
79 m_fd = -1;
80}
81
82TransferWltoX::TransferWltoX(xcb_atom_t selection, xcb_selection_request_event_t *request,
83 qint32 fd, QObject *parent)
84 : Transfer(selection, fd, 0, parent)
85 , m_request(request)
86{
87}
88
90{
91 delete m_request;
92 m_request = nullptr;
93}
94
96{
97 createSocketNotifier(QSocketNotifier::Read);
98 connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) {
99 readWlSource();
100 });
101}
102
103int TransferWltoX::flushSourceData()
104{
105 Q_ASSERT(!m_chunks.isEmpty());
106 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
107
108 xcb_change_property(xcbConn,
109 XCB_PROP_MODE_REPLACE,
110 m_request->requestor,
111 m_request->property,
112 m_request->target,
113 8,
114 m_chunks.first().first.size(),
115 m_chunks.first().first.data());
116 xcb_flush(xcbConn);
117
118 m_propertyIsSet = true;
119 resetTimeout();
120
121 const auto rm = m_chunks.takeFirst();
122 return rm.first.size();
123}
124
125void TransferWltoX::startIncr()
126{
127 Q_ASSERT(m_chunks.size() == 1);
128
129 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
130
131 uint32_t mask[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
132 xcb_change_window_attributes(xcbConn,
133 m_request->requestor,
134 XCB_CW_EVENT_MASK, mask);
135
136 // spec says to make the available space larger
137 const uint32_t chunkSpace = 1024 + s_incrChunkSize;
138 xcb_change_property(xcbConn,
139 XCB_PROP_MODE_REPLACE,
140 m_request->requestor,
141 m_request->property,
142 atoms->incr,
143 32, 1, &chunkSpace);
144 xcb_flush(xcbConn);
145
146 setIncr(true);
147 // first data will be flushed after the property has been deleted
148 // again by the requestor
149 m_flushPropertyOnDelete = true;
150 m_propertyIsSet = true;
151 Q_EMIT selectionNotify(m_request, true);
152}
153
154void TransferWltoX::readWlSource()
155{
156 if (m_chunks.size() == 0 || m_chunks.last().second == s_incrChunkSize) {
157 // append new chunk
158 auto next = QPair<QByteArray, int>();
159 next.first.resize(s_incrChunkSize);
160 next.second = 0;
161 m_chunks.append(next);
162 }
163
164 const auto oldLen = m_chunks.last().second;
165 const auto avail = s_incrChunkSize - m_chunks.last().second;
166 Q_ASSERT(avail > 0);
167
168 ssize_t readLen = read(fd(), m_chunks.last().first.data() + oldLen, avail);
169 if (readLen == -1) {
170 qCWarning(KWIN_XWL) << "Error reading in Wl data.";
171
172 // TODO: cleanup X side?
173 endTransfer();
174 return;
175 }
176 m_chunks.last().second = oldLen + readLen;
177
178 if (readLen == 0) {
179 // at the fd end - complete transfer now
180 m_chunks.last().first.resize(m_chunks.last().second);
181
182 if (incr()) {
183 // incremental transfer is to be completed now
184 m_flushPropertyOnDelete = true;
185 if (!m_propertyIsSet) {
186 // flush if target's property is not set at the moment
187 flushSourceData();
188 }
190 } else {
191 // non incremental transfer is to be completed now,
192 // data can be transferred to X client via a single property set
193 flushSourceData();
194 Q_EMIT selectionNotify(m_request, true);
195 endTransfer();
196 }
197 } else if (m_chunks.last().second == s_incrChunkSize) {
198 // first chunk full, but not yet at fd end -> go incremental
199 if (incr()) {
200 m_flushPropertyOnDelete = true;
201 if (!m_propertyIsSet) {
202 // flush if target's property is not set at the moment
203 flushSourceData();
204 }
205 } else {
206 // starting incremental transfer
207 startIncr();
208 }
209 }
210 resetTimeout();
211}
212
213bool TransferWltoX::handlePropertyNotify(xcb_property_notify_event_t *event)
214{
215 if (event->window == m_request->requestor) {
216 if (event->state == XCB_PROPERTY_DELETE && event->atom == m_request->property) {
217 handlePropertyDelete();
218 }
219 return true;
220 }
221 return false;
222}
223
224void TransferWltoX::handlePropertyDelete()
225{
226 if (!incr()) {
227 // non-incremental transfer: nothing to do
228 return;
229 }
230 m_propertyIsSet = false;
231
232 if (m_flushPropertyOnDelete) {
233 if (!socketNotifier() && m_chunks.isEmpty()) {
234 // transfer complete
235 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
236
237 uint32_t mask[] = {0};
238 xcb_change_window_attributes(xcbConn,
239 m_request->requestor,
240 XCB_CW_EVENT_MASK, mask);
241
242 xcb_change_property(xcbConn,
243 XCB_PROP_MODE_REPLACE,
244 m_request->requestor,
245 m_request->property,
246 m_request->target,
247 8, 0, nullptr);
248 xcb_flush(xcbConn);
249 m_flushPropertyOnDelete = false;
250 endTransfer();
251 } else if (!m_chunks.isEmpty()) {
252 flushSourceData();
253 }
254 }
255}
256
257TransferXtoWl::TransferXtoWl(xcb_atom_t selection, xcb_atom_t target, qint32 fd,
258 xcb_timestamp_t timestamp, xcb_window_t parentWindow,
259 QObject *parent)
260 : Transfer(selection, fd, timestamp, parent)
261{
262 // create transfer window
263 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
264 m_window = xcb_generate_id(xcbConn);
265 const uint32_t values[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
266 xcb_create_window(xcbConn,
267 XCB_COPY_FROM_PARENT,
268 m_window,
269 parentWindow,
270 0, 0,
271 10, 10,
272 0,
273 XCB_WINDOW_CLASS_INPUT_OUTPUT,
274 XCB_COPY_FROM_PARENT,
275 XCB_CW_EVENT_MASK,
276 values);
277 // convert selection
278 xcb_convert_selection(xcbConn,
279 m_window,
280 selection,
281 target,
283 timestamp);
284 xcb_flush(xcbConn);
285}
286
288{
289 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
290 xcb_destroy_window(xcbConn, m_window);
291 xcb_flush(xcbConn);
292
293 delete m_receiver;
294 m_receiver = nullptr;
295}
296
297bool TransferXtoWl::handlePropertyNotify(xcb_property_notify_event_t *event)
298{
299 if (event->window == m_window) {
300 if (event->state == XCB_PROPERTY_NEW_VALUE && event->atom == atoms->wl_selection) {
301 getIncrChunk();
302 }
303 return true;
304 }
305 return false;
306}
307
308bool TransferXtoWl::handleSelectionNotify(xcb_selection_notify_event_t *event)
309{
310 if (event->requestor != m_window) {
311 return false;
312 }
313 if (event->selection != atom()) {
314 return false;
315 }
316 if (event->property == XCB_ATOM_NONE) {
317 qCWarning(KWIN_XWL) << "Incoming X selection conversion failed";
318 return true;
319 }
320 if (event->target == atoms->targets) {
321 qCWarning(KWIN_XWL) << "Received targets too late";
322 // TODO: or allow it?
323 return true;
324 }
325 if (m_receiver) {
326 // second selection notify element - misbehaving source
327
328 // TODO: cancel this transfer?
329 return true;
330 }
331
332 m_receiver = new DataReceiver;
333
334 startTransfer();
335 return true;
336}
337
338void TransferXtoWl::startTransfer()
339{
340 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
341 auto cookie = xcb_get_property(xcbConn,
342 1,
343 m_window,
345 XCB_GET_PROPERTY_TYPE_ANY,
346 0,
347 0x1fffffff);
348
349 auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
350 if (reply == nullptr) {
351 qCWarning(KWIN_XWL) << "Can't get selection property.";
352 endTransfer();
353 return;
354 }
355
356 if (reply->type == atoms->incr) {
357 setIncr(true);
358 free(reply);
359 } else {
360 setIncr(false);
361 // reply's ownership is transferred
362 m_receiver->transferFromProperty(reply);
363 dataSourceWrite();
364 }
365}
366
367void TransferXtoWl::getIncrChunk()
368{
369 if (!incr()) {
370 // source tries to sent incrementally, but did not announce it before
371 return;
372 }
373 if (!m_receiver) {
374 // receive mechanism has not yet been setup
375 return;
376 }
377 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
378
379 auto cookie = xcb_get_property(xcbConn,
380 0,
381 m_window,
383 XCB_GET_PROPERTY_TYPE_ANY,
384 0,
385 0x1fffffff);
386
387 auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
388 if (!reply) {
389 qCWarning(KWIN_XWL) << "Can't get selection property.";
390 endTransfer();
391 return;
392 }
393
394 if (xcb_get_property_value_length(reply) > 0) {
395 // reply's ownership is transferred
396 m_receiver->transferFromProperty(reply);
397 dataSourceWrite();
398 } else {
399 // Transfer complete
400 free(reply);
401 endTransfer();
402 }
403}
404
406{
407 if (m_propertyReply) {
408 free(m_propertyReply);
409 m_propertyReply = nullptr;
410 }
411}
412
413void DataReceiver::transferFromProperty(xcb_get_property_reply_t *reply)
414{
415 m_propertyStart = 0;
416 m_propertyReply = reply;
417
418 setData(static_cast<char *>(xcb_get_property_value(reply)),
419 xcb_get_property_value_length(reply));
420}
421
422void DataReceiver::setData(const char *value, int length)
423{
424 // simply set data without copy
425 m_data = QByteArray::fromRawData(value, length);
426}
427
428QByteArray DataReceiver::data() const
429{
430 return QByteArray::fromRawData(m_data.data() + m_propertyStart,
431 m_data.size() - m_propertyStart);
432}
433
435{
436 m_propertyStart += length;
437 if (m_propertyStart == m_data.size()) {
438 Q_ASSERT(m_propertyReply);
439 free(m_propertyReply);
440 m_propertyReply = nullptr;
441 }
442}
443
444void TransferXtoWl::dataSourceWrite()
445{
446 QByteArray property = m_receiver->data();
447
448 ssize_t len = write(fd(), property.constData(), property.size());
449 if (len == -1) {
450 qCWarning(KWIN_XWL) << "X11 to Wayland write error on fd:" << fd();
451 endTransfer();
452 return;
453 }
454
455 m_receiver->partRead(len);
456 if (len == property.size()) {
457 // property completely transferred
458 if (incr()) {
460 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
461 xcb_delete_property(xcbConn,
462 m_window,
464 xcb_flush(xcbConn);
465 } else {
466 // transfer complete
467 endTransfer();
468 }
469 } else {
470 if (!socketNotifier()) {
471 createSocketNotifier(QSocketNotifier::Write);
472 connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) {
473 dataSourceWrite();
474 });
475 }
476 }
477 resetTimeout();
478}
479
480} // namespace Xwl
481} // namespace KWin
482
483#include "moc_transfer.cpp"
Xcb::Atom incr
Definition atoms.h:75
Xcb::Atom wl_selection
Definition atoms.h:76
Xcb::Atom targets
Definition atoms.h:73
QByteArray data() const
Definition transfer.cpp:428
void transferFromProperty(xcb_get_property_reply_t *reply)
Definition transfer.cpp:413
void setData(const char *value, int length)
Definition transfer.cpp:422
void partRead(int length)
Definition transfer.cpp:434
bool incr() const
Definition transfer.h:76
xcb_timestamp_t timestamp() const
Definition transfer.h:52
void clearSocketNotifier()
Definition transfer.cpp:52
void setIncr(bool set)
Definition transfer.h:72
QSocketNotifier * socketNotifier() const
Definition transfer.h:86
qint32 fd() const
Definition transfer.h:67
Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject *parent=nullptr)
Definition transfer.cpp:38
void createSocketNotifier(QSocketNotifier::Type type)
Definition transfer.cpp:46
xcb_atom_t atom() const
Definition transfer.h:63
bool handlePropertyNotify(xcb_property_notify_event_t *event) override
Definition transfer.cpp:213
TransferWltoX(xcb_atom_t selection, xcb_selection_request_event_t *request, qint32 fd, QObject *parent=nullptr)
Definition transfer.cpp:82
void selectionNotify(xcb_selection_request_event_t *event, bool success)
bool handleSelectionNotify(xcb_selection_notify_event_t *event)
Definition transfer.cpp:308
TransferXtoWl(xcb_atom_t selection, xcb_atom_t target, qint32 fd, xcb_timestamp_t timestamp, xcb_window_t parentWindow, QObject *parent=nullptr)
Definition transfer.cpp:257
bool handlePropertyNotify(xcb_property_notify_event_t *event) override
Definition transfer.cpp:297
Session::Type type
Definition session.cpp:17
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74