File indexing completed on 2024-05-12 05:32:35

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "selection.h"
0010 #include "databridge.h"
0011 #include "selection_source.h"
0012 #include "transfer.h"
0013 
0014 #include "atoms.h"
0015 #include "utils/xcbutils.h"
0016 #include "workspace.h"
0017 #include "x11window.h"
0018 
0019 #include <xcb/xcb_event.h>
0020 #include <xcb/xfixes.h>
0021 
0022 #include <QTimer>
0023 
0024 namespace KWin
0025 {
0026 namespace Xwl
0027 {
0028 
0029 xcb_atom_t Selection::mimeTypeToAtom(const QString &mimeType)
0030 {
0031     if (mimeType == QLatin1String("text/plain;charset=utf-8")) {
0032         return atoms->utf8_string;
0033     }
0034     if (mimeType == QLatin1String("text/plain")) {
0035         return atoms->text;
0036     }
0037     if (mimeType == QLatin1String("text/x-uri")) {
0038         return atoms->uri_list;
0039     }
0040     return mimeTypeToAtomLiteral(mimeType);
0041 }
0042 
0043 xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType)
0044 {
0045     return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection());
0046 }
0047 
0048 QString Selection::atomName(xcb_atom_t atom)
0049 {
0050     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0051     xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom);
0052     xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, nullptr);
0053     if (!nameReply) {
0054         return QString();
0055     }
0056 
0057     const size_t length = xcb_get_atom_name_name_length(nameReply);
0058     QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), length);
0059     free(nameReply);
0060     return name;
0061 }
0062 
0063 QStringList Selection::atomToMimeTypes(xcb_atom_t atom)
0064 {
0065     QStringList mimeTypes;
0066 
0067     if (atom == atoms->utf8_string) {
0068         mimeTypes << QString::fromLatin1("text/plain;charset=utf-8");
0069     } else if (atom == atoms->text) {
0070         mimeTypes << QString::fromLatin1("text/plain");
0071     } else if (atom == atoms->uri_list) {
0072         mimeTypes << "text/uri-list"
0073                   << "text/x-uri";
0074     } else {
0075         mimeTypes << atomName(atom);
0076     }
0077     return mimeTypes;
0078 }
0079 
0080 Selection::Selection(xcb_atom_t atom, QObject *parent)
0081     : QObject(parent)
0082     , m_atom(atom)
0083 {
0084     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0085     m_window = xcb_generate_id(kwinApp()->x11Connection());
0086     m_requestorWindow = m_window;
0087     xcb_flush(xcbConn);
0088 }
0089 
0090 bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
0091 {
0092     if (event->window != m_window) {
0093         return false;
0094     }
0095     if (event->selection != m_atom) {
0096         return false;
0097     }
0098     if (m_disownPending) {
0099         // notify of our own disown - ignore it
0100         m_disownPending = false;
0101         return true;
0102     }
0103     if (event->owner == m_window && m_waylandSource) {
0104         // When we claim a selection we must use XCB_TIME_CURRENT,
0105         // grab the actual timestamp here to answer TIMESTAMP requests
0106         // correctly
0107         m_waylandSource->setTimestamp(event->timestamp);
0108         m_timestamp = event->timestamp;
0109         return true;
0110     }
0111 
0112     // Being here means some other X window has claimed the selection.
0113     doHandleXfixesNotify(event);
0114     return true;
0115 }
0116 
0117 bool Selection::filterEvent(xcb_generic_event_t *event)
0118 {
0119     switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
0120     case XCB_SELECTION_NOTIFY:
0121         return handleSelectionNotify(reinterpret_cast<xcb_selection_notify_event_t *>(event));
0122     case XCB_PROPERTY_NOTIFY:
0123         return handlePropertyNotify(reinterpret_cast<xcb_property_notify_event_t *>(event));
0124     case XCB_SELECTION_REQUEST:
0125         return handleSelectionRequest(reinterpret_cast<xcb_selection_request_event_t *>(event));
0126     case XCB_CLIENT_MESSAGE:
0127         return handleClientMessage(reinterpret_cast<xcb_client_message_event_t *>(event));
0128     default:
0129         if (event->response_type == Xcb::Extensions::self()->fixesSelectionNotifyEvent()) {
0130             return handleXfixesNotify(reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event));
0131         }
0132         return false;
0133     }
0134 }
0135 
0136 void Selection::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
0137 {
0138     // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
0139     // 32 unconditionally. Use a union to ensure we don't disclose stack memory.
0140     union {
0141         xcb_selection_notify_event_t notify;
0142         char buffer[32];
0143     } u;
0144     memset(&u, 0, sizeof(u));
0145     static_assert(sizeof(u.notify) < 32, "wouldn't need the union otherwise");
0146     u.notify.response_type = XCB_SELECTION_NOTIFY;
0147     u.notify.sequence = 0;
0148     u.notify.time = event->time;
0149     u.notify.requestor = event->requestor;
0150     u.notify.selection = event->selection;
0151     u.notify.target = event->target;
0152     u.notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE);
0153 
0154     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0155     xcb_send_event(xcbConn, 0, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&u);
0156     xcb_flush(xcbConn);
0157 }
0158 
0159 void Selection::registerXfixes()
0160 {
0161     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0162     const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
0163     xcb_xfixes_select_selection_input(xcbConn,
0164                                       m_window,
0165                                       m_atom,
0166                                       mask);
0167     xcb_flush(xcbConn);
0168 }
0169 
0170 void Selection::setWlSource(WlSource *source)
0171 {
0172     if (m_waylandSource) {
0173         m_waylandSource->deleteLater();
0174         m_waylandSource = nullptr;
0175     }
0176     delete m_xSource;
0177     m_xSource = nullptr;
0178     if (source) {
0179         m_waylandSource = source;
0180         connect(source, &WlSource::transferReady, this, &Selection::startTransferToX);
0181     }
0182 }
0183 
0184 void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event)
0185 {
0186     if (!event || event->owner == XCB_WINDOW_NONE) {
0187         x11OfferLost();
0188         setWlSource(nullptr);
0189         return;
0190     }
0191     setWlSource(nullptr);
0192 
0193     m_xSource = new X11Source(this, event);
0194     connect(m_xSource, &X11Source::offersChanged, this, &Selection::x11OffersChanged);
0195     connect(m_xSource, &X11Source::transferReady, this, &Selection::startTransferToWayland);
0196 }
0197 
0198 void Selection::ownSelection(bool own)
0199 {
0200     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0201     if (own) {
0202         xcb_set_selection_owner(xcbConn,
0203                                 m_window,
0204                                 m_atom,
0205                                 XCB_TIME_CURRENT_TIME);
0206     } else {
0207         m_disownPending = true;
0208         xcb_set_selection_owner(xcbConn,
0209                                 XCB_WINDOW_NONE,
0210                                 m_atom,
0211                                 m_timestamp);
0212     }
0213     xcb_flush(xcbConn);
0214 }
0215 
0216 void Selection::overwriteRequestorWindow(xcb_window_t window)
0217 {
0218     Q_ASSERT(m_xSource);
0219     if (window == XCB_WINDOW_NONE) {
0220         // reset
0221         window = m_window;
0222     }
0223     m_requestorWindow = window;
0224     m_xSource->setRequestor(window);
0225 }
0226 
0227 bool Selection::handleSelectionRequest(xcb_selection_request_event_t *event)
0228 {
0229     if (event->selection != m_atom) {
0230         return false;
0231     }
0232 
0233     if (qobject_cast<X11Window *>(workspace()->activeWindow()) == nullptr) {
0234         // Receiving Wayland selection not allowed when no Xwayland surface active
0235         // filter the event, but don't act upon it
0236         sendSelectionNotify(event, false);
0237         return true;
0238     }
0239 
0240     if (m_window != event->owner || !m_waylandSource) {
0241         if (event->time < m_timestamp) {
0242             // cancel earlier attempts at receiving a selection
0243             // TODO: is this for sure without problems?
0244             sendSelectionNotify(event, false);
0245             return true;
0246         }
0247         return false;
0248     }
0249     return m_waylandSource->handleSelectionRequest(event);
0250 }
0251 
0252 bool Selection::handleSelectionNotify(xcb_selection_notify_event_t *event)
0253 {
0254     if (m_xSource && m_xSource->handleSelectionNotify(event)) {
0255         return true;
0256     }
0257     for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
0258         if (transfer->handleSelectionNotify(event)) {
0259             return true;
0260         }
0261     }
0262     return false;
0263 }
0264 
0265 bool Selection::handlePropertyNotify(xcb_property_notify_event_t *event)
0266 {
0267     for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
0268         if (transfer->handlePropertyNotify(event)) {
0269             return true;
0270         }
0271     }
0272     for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) {
0273         if (transfer->handlePropertyNotify(event)) {
0274             return true;
0275         }
0276     }
0277     return false;
0278 }
0279 
0280 void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd)
0281 {
0282     // create new x to wl data transfer object
0283     auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSource->timestamp(), m_requestorWindow, this);
0284     m_xToWlTransfers << transfer;
0285 
0286     connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() {
0287         Q_EMIT transferFinished(transfer->timestamp());
0288         transfer->deleteLater();
0289         m_xToWlTransfers.removeOne(transfer);
0290         endTimeoutTransfersTimer();
0291     });
0292     startTimeoutTransfersTimer();
0293 }
0294 
0295 void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd)
0296 {
0297     // create new wl to x data transfer object
0298     auto *transfer = new TransferWltoX(m_atom, event, fd, this);
0299 
0300     connect(transfer, &TransferWltoX::selectionNotify, this, &Selection::sendSelectionNotify);
0301     connect(transfer, &TransferWltoX::finished, this, [this, transfer]() {
0302         Q_EMIT transferFinished(transfer->timestamp());
0303 
0304         // TODO: serialize? see comment below.
0305         //        const bool wasActive = (transfer == m_wlToXTransfers[0]);
0306         transfer->deleteLater();
0307         m_wlToXTransfers.removeOne(transfer);
0308         endTimeoutTransfersTimer();
0309         //        if (wasActive && !m_wlToXTransfers.isEmpty()) {
0310         //            m_wlToXTransfers[0]->startTransferFromSource();
0311         //        }
0312     });
0313 
0314     // add it to list of queued transfers
0315     m_wlToXTransfers.append(transfer);
0316 
0317     // TODO: Do we need to serialize the transfers, or can we do
0318     //       them in parallel as we do it right now?
0319     transfer->startTransferFromSource();
0320     //    if (m_wlToXTransfers.size() == 1) {
0321     //        transfer->startTransferFromSource();
0322     //    }
0323     startTimeoutTransfersTimer();
0324 }
0325 
0326 void Selection::startTimeoutTransfersTimer()
0327 {
0328     if (m_timeoutTransfers) {
0329         return;
0330     }
0331     m_timeoutTransfers = new QTimer(this);
0332     connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers);
0333     m_timeoutTransfers->start(5000);
0334 }
0335 
0336 void Selection::endTimeoutTransfersTimer()
0337 {
0338     if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) {
0339         delete m_timeoutTransfers;
0340         m_timeoutTransfers = nullptr;
0341     }
0342 }
0343 
0344 void Selection::timeoutTransfers()
0345 {
0346     for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
0347         transfer->timeout();
0348     }
0349     for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) {
0350         transfer->timeout();
0351     }
0352 }
0353 
0354 } // namespace Xwl
0355 } // namespace KWin
0356 
0357 #include "moc_selection.cpp"