File indexing completed on 2024-05-19 05:32:51

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "transfer.h"
0010 
0011 #include "databridge.h"
0012 #include "xwayland.h"
0013 
0014 #include "atoms.h"
0015 #include "wayland/datadevice.h"
0016 #include "wayland/datasource.h"
0017 #include "wayland/seat.h"
0018 #include "wayland_server.h"
0019 #include "window.h"
0020 #include "workspace.h"
0021 
0022 #include <xcb/xcb_event.h>
0023 #include <xcb/xfixes.h>
0024 
0025 #include <algorithm>
0026 #include <unistd.h>
0027 
0028 #include <xwayland_logging.h>
0029 
0030 namespace KWin
0031 {
0032 namespace Xwl
0033 {
0034 
0035 // in Bytes: equals 64KB
0036 static const uint32_t s_incrChunkSize = 63 * 1024;
0037 
0038 Transfer::Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject *parent)
0039     : QObject(parent)
0040     , m_atom(selection)
0041     , m_fd(fd)
0042     , m_timestamp(timestamp)
0043 {
0044 }
0045 
0046 void Transfer::createSocketNotifier(QSocketNotifier::Type type)
0047 {
0048     delete m_notifier;
0049     m_notifier = new QSocketNotifier(m_fd, type, this);
0050 }
0051 
0052 void Transfer::clearSocketNotifier()
0053 {
0054     delete m_notifier;
0055     m_notifier = nullptr;
0056 }
0057 
0058 void Transfer::timeout()
0059 {
0060     if (m_timeout) {
0061         endTransfer();
0062     }
0063     m_timeout = true;
0064 }
0065 
0066 void Transfer::endTransfer()
0067 {
0068     clearSocketNotifier();
0069     closeFd();
0070     Q_EMIT finished();
0071 }
0072 
0073 void Transfer::closeFd()
0074 {
0075     if (m_fd < 0) {
0076         return;
0077     }
0078     close(m_fd);
0079     m_fd = -1;
0080 }
0081 
0082 TransferWltoX::TransferWltoX(xcb_atom_t selection, xcb_selection_request_event_t *request,
0083                              qint32 fd, QObject *parent)
0084     : Transfer(selection, fd, 0, parent)
0085     , m_request(request)
0086 {
0087 }
0088 
0089 TransferWltoX::~TransferWltoX()
0090 {
0091     delete m_request;
0092     m_request = nullptr;
0093 }
0094 
0095 void TransferWltoX::startTransferFromSource()
0096 {
0097     createSocketNotifier(QSocketNotifier::Read);
0098     connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) {
0099         readWlSource();
0100     });
0101 }
0102 
0103 int TransferWltoX::flushSourceData()
0104 {
0105     Q_ASSERT(!m_chunks.isEmpty());
0106     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0107 
0108     xcb_change_property(xcbConn,
0109                         XCB_PROP_MODE_REPLACE,
0110                         m_request->requestor,
0111                         m_request->property,
0112                         m_request->target,
0113                         8,
0114                         m_chunks.first().first.size(),
0115                         m_chunks.first().first.data());
0116     xcb_flush(xcbConn);
0117 
0118     m_propertyIsSet = true;
0119     resetTimeout();
0120 
0121     const auto rm = m_chunks.takeFirst();
0122     return rm.first.size();
0123 }
0124 
0125 void TransferWltoX::startIncr()
0126 {
0127     Q_ASSERT(m_chunks.size() == 1);
0128 
0129     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0130 
0131     uint32_t mask[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
0132     xcb_change_window_attributes(xcbConn,
0133                                  m_request->requestor,
0134                                  XCB_CW_EVENT_MASK, mask);
0135 
0136     // spec says to make the available space larger
0137     const uint32_t chunkSpace = 1024 + s_incrChunkSize;
0138     xcb_change_property(xcbConn,
0139                         XCB_PROP_MODE_REPLACE,
0140                         m_request->requestor,
0141                         m_request->property,
0142                         atoms->incr,
0143                         32, 1, &chunkSpace);
0144     xcb_flush(xcbConn);
0145 
0146     setIncr(true);
0147     // first data will be flushed after the property has been deleted
0148     // again by the requestor
0149     m_flushPropertyOnDelete = true;
0150     m_propertyIsSet = true;
0151     Q_EMIT selectionNotify(m_request, true);
0152 }
0153 
0154 void TransferWltoX::readWlSource()
0155 {
0156     if (m_chunks.size() == 0 || m_chunks.last().second == s_incrChunkSize) {
0157         // append new chunk
0158         auto next = QPair<QByteArray, int>();
0159         next.first.resize(s_incrChunkSize);
0160         next.second = 0;
0161         m_chunks.append(next);
0162     }
0163 
0164     const auto oldLen = m_chunks.last().second;
0165     const auto avail = s_incrChunkSize - m_chunks.last().second;
0166     Q_ASSERT(avail > 0);
0167 
0168     ssize_t readLen = read(fd(), m_chunks.last().first.data() + oldLen, avail);
0169     if (readLen == -1) {
0170         qCWarning(KWIN_XWL) << "Error reading in Wl data.";
0171 
0172         // TODO: cleanup X side?
0173         endTransfer();
0174         return;
0175     }
0176     m_chunks.last().second = oldLen + readLen;
0177 
0178     if (readLen == 0) {
0179         // at the fd end - complete transfer now
0180         m_chunks.last().first.resize(m_chunks.last().second);
0181 
0182         if (incr()) {
0183             // incremental transfer is to be completed now
0184             m_flushPropertyOnDelete = true;
0185             if (!m_propertyIsSet) {
0186                 // flush if target's property is not set at the moment
0187                 flushSourceData();
0188             }
0189             clearSocketNotifier();
0190         } else {
0191             // non incremental transfer is to be completed now,
0192             // data can be transferred to X client via a single property set
0193             flushSourceData();
0194             Q_EMIT selectionNotify(m_request, true);
0195             endTransfer();
0196         }
0197     } else if (m_chunks.last().second == s_incrChunkSize) {
0198         // first chunk full, but not yet at fd end -> go incremental
0199         if (incr()) {
0200             m_flushPropertyOnDelete = true;
0201             if (!m_propertyIsSet) {
0202                 // flush if target's property is not set at the moment
0203                 flushSourceData();
0204             }
0205         } else {
0206             // starting incremental transfer
0207             startIncr();
0208         }
0209     }
0210     resetTimeout();
0211 }
0212 
0213 bool TransferWltoX::handlePropertyNotify(xcb_property_notify_event_t *event)
0214 {
0215     if (event->window == m_request->requestor) {
0216         if (event->state == XCB_PROPERTY_DELETE && event->atom == m_request->property) {
0217             handlePropertyDelete();
0218         }
0219         return true;
0220     }
0221     return false;
0222 }
0223 
0224 void TransferWltoX::handlePropertyDelete()
0225 {
0226     if (!incr()) {
0227         // non-incremental transfer: nothing to do
0228         return;
0229     }
0230     m_propertyIsSet = false;
0231 
0232     if (m_flushPropertyOnDelete) {
0233         if (!socketNotifier() && m_chunks.isEmpty()) {
0234             // transfer complete
0235             xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0236 
0237             uint32_t mask[] = {0};
0238             xcb_change_window_attributes(xcbConn,
0239                                          m_request->requestor,
0240                                          XCB_CW_EVENT_MASK, mask);
0241 
0242             xcb_change_property(xcbConn,
0243                                 XCB_PROP_MODE_REPLACE,
0244                                 m_request->requestor,
0245                                 m_request->property,
0246                                 m_request->target,
0247                                 8, 0, nullptr);
0248             xcb_flush(xcbConn);
0249             m_flushPropertyOnDelete = false;
0250             endTransfer();
0251         } else if (!m_chunks.isEmpty()) {
0252             flushSourceData();
0253         }
0254     }
0255 }
0256 
0257 TransferXtoWl::TransferXtoWl(xcb_atom_t selection, xcb_atom_t target, qint32 fd,
0258                              xcb_timestamp_t timestamp, xcb_window_t parentWindow,
0259                              QObject *parent)
0260     : Transfer(selection, fd, timestamp, parent)
0261 {
0262     // create transfer window
0263     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0264     m_window = xcb_generate_id(xcbConn);
0265     const uint32_t values[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
0266     xcb_create_window(xcbConn,
0267                       XCB_COPY_FROM_PARENT,
0268                       m_window,
0269                       parentWindow,
0270                       0, 0,
0271                       10, 10,
0272                       0,
0273                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
0274                       XCB_COPY_FROM_PARENT,
0275                       XCB_CW_EVENT_MASK,
0276                       values);
0277     // convert selection
0278     xcb_convert_selection(xcbConn,
0279                           m_window,
0280                           selection,
0281                           target,
0282                           atoms->wl_selection,
0283                           timestamp);
0284     xcb_flush(xcbConn);
0285 }
0286 
0287 TransferXtoWl::~TransferXtoWl()
0288 {
0289     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0290     xcb_destroy_window(xcbConn, m_window);
0291     xcb_flush(xcbConn);
0292 
0293     delete m_receiver;
0294     m_receiver = nullptr;
0295 }
0296 
0297 bool TransferXtoWl::handlePropertyNotify(xcb_property_notify_event_t *event)
0298 {
0299     if (event->window == m_window) {
0300         if (event->state == XCB_PROPERTY_NEW_VALUE && event->atom == atoms->wl_selection) {
0301             getIncrChunk();
0302         }
0303         return true;
0304     }
0305     return false;
0306 }
0307 
0308 bool TransferXtoWl::handleSelectionNotify(xcb_selection_notify_event_t *event)
0309 {
0310     if (event->requestor != m_window) {
0311         return false;
0312     }
0313     if (event->selection != atom()) {
0314         return false;
0315     }
0316     if (event->property == XCB_ATOM_NONE) {
0317         qCWarning(KWIN_XWL) << "Incoming X selection conversion failed";
0318         return true;
0319     }
0320     if (event->target == atoms->targets) {
0321         qCWarning(KWIN_XWL) << "Received targets too late";
0322         // TODO: or allow it?
0323         return true;
0324     }
0325     if (m_receiver) {
0326         // second selection notify element - misbehaving source
0327 
0328         // TODO: cancel this transfer?
0329         return true;
0330     }
0331 
0332     m_receiver = new DataReceiver;
0333 
0334     startTransfer();
0335     return true;
0336 }
0337 
0338 void TransferXtoWl::startTransfer()
0339 {
0340     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0341     auto cookie = xcb_get_property(xcbConn,
0342                                    1,
0343                                    m_window,
0344                                    atoms->wl_selection,
0345                                    XCB_GET_PROPERTY_TYPE_ANY,
0346                                    0,
0347                                    0x1fffffff);
0348 
0349     auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
0350     if (reply == nullptr) {
0351         qCWarning(KWIN_XWL) << "Can't get selection property.";
0352         endTransfer();
0353         return;
0354     }
0355 
0356     if (reply->type == atoms->incr) {
0357         setIncr(true);
0358         free(reply);
0359     } else {
0360         setIncr(false);
0361         // reply's ownership is transferred
0362         m_receiver->transferFromProperty(reply);
0363         dataSourceWrite();
0364     }
0365 }
0366 
0367 void TransferXtoWl::getIncrChunk()
0368 {
0369     if (!incr()) {
0370         // source tries to sent incrementally, but did not announce it before
0371         return;
0372     }
0373     if (!m_receiver) {
0374         // receive mechanism has not yet been setup
0375         return;
0376     }
0377     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0378 
0379     auto cookie = xcb_get_property(xcbConn,
0380                                    0,
0381                                    m_window,
0382                                    atoms->wl_selection,
0383                                    XCB_GET_PROPERTY_TYPE_ANY,
0384                                    0,
0385                                    0x1fffffff);
0386 
0387     auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
0388     if (!reply) {
0389         qCWarning(KWIN_XWL) << "Can't get selection property.";
0390         endTransfer();
0391         return;
0392     }
0393 
0394     if (xcb_get_property_value_length(reply) > 0) {
0395         // reply's ownership is transferred
0396         m_receiver->transferFromProperty(reply);
0397         dataSourceWrite();
0398     } else {
0399         // Transfer complete
0400         free(reply);
0401         endTransfer();
0402     }
0403 }
0404 
0405 DataReceiver::~DataReceiver()
0406 {
0407     if (m_propertyReply) {
0408         free(m_propertyReply);
0409         m_propertyReply = nullptr;
0410     }
0411 }
0412 
0413 void DataReceiver::transferFromProperty(xcb_get_property_reply_t *reply)
0414 {
0415     m_propertyStart = 0;
0416     m_propertyReply = reply;
0417 
0418     setData(static_cast<char *>(xcb_get_property_value(reply)),
0419             xcb_get_property_value_length(reply));
0420 }
0421 
0422 void DataReceiver::setData(const char *value, int length)
0423 {
0424     // simply set data without copy
0425     m_data = QByteArray::fromRawData(value, length);
0426 }
0427 
0428 QByteArray DataReceiver::data() const
0429 {
0430     return QByteArray::fromRawData(m_data.data() + m_propertyStart,
0431                                    m_data.size() - m_propertyStart);
0432 }
0433 
0434 void DataReceiver::partRead(int length)
0435 {
0436     m_propertyStart += length;
0437     if (m_propertyStart == m_data.size()) {
0438         Q_ASSERT(m_propertyReply);
0439         free(m_propertyReply);
0440         m_propertyReply = nullptr;
0441     }
0442 }
0443 
0444 void TransferXtoWl::dataSourceWrite()
0445 {
0446     QByteArray property = m_receiver->data();
0447 
0448     ssize_t len = write(fd(), property.constData(), property.size());
0449     if (len == -1) {
0450         qCWarning(KWIN_XWL) << "X11 to Wayland write error on fd:" << fd();
0451         endTransfer();
0452         return;
0453     }
0454 
0455     m_receiver->partRead(len);
0456     if (len == property.size()) {
0457         // property completely transferred
0458         if (incr()) {
0459             clearSocketNotifier();
0460             xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0461             xcb_delete_property(xcbConn,
0462                                 m_window,
0463                                 atoms->wl_selection);
0464             xcb_flush(xcbConn);
0465         } else {
0466             // transfer complete
0467             endTransfer();
0468         }
0469     } else {
0470         if (!socketNotifier()) {
0471             createSocketNotifier(QSocketNotifier::Write);
0472             connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) {
0473                 dataSourceWrite();
0474             });
0475         }
0476     }
0477     resetTimeout();
0478 }
0479 
0480 } // namespace Xwl
0481 } // namespace KWin
0482 
0483 #include "moc_transfer.cpp"