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