File indexing completed on 2024-05-19 16:35:36

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     setWlSource(nullptr);
0187     if (!event || event->owner == XCB_WINDOW_NONE) {
0188         return;
0189     }
0190     m_xSource = new X11Source(this, event);
0191 
0192     connect(m_xSource, &X11Source::offersChanged, this, &Selection::x11OffersChanged);
0193     connect(m_xSource, &X11Source::transferReady, this, &Selection::startTransferToWayland);
0194 }
0195 
0196 void Selection::ownSelection(bool own)
0197 {
0198     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0199     if (own) {
0200         xcb_set_selection_owner(xcbConn,
0201                                 m_window,
0202                                 m_atom,
0203                                 XCB_TIME_CURRENT_TIME);
0204     } else {
0205         m_disownPending = true;
0206         xcb_set_selection_owner(xcbConn,
0207                                 XCB_WINDOW_NONE,
0208                                 m_atom,
0209                                 m_timestamp);
0210     }
0211     xcb_flush(xcbConn);
0212 }
0213 
0214 void Selection::overwriteRequestorWindow(xcb_window_t window)
0215 {
0216     Q_ASSERT(m_xSource);
0217     if (window == XCB_WINDOW_NONE) {
0218         // reset
0219         window = m_window;
0220     }
0221     m_requestorWindow = window;
0222     m_xSource->setRequestor(window);
0223 }
0224 
0225 bool Selection::handleSelectionRequest(xcb_selection_request_event_t *event)
0226 {
0227     if (event->selection != m_atom) {
0228         return false;
0229     }
0230 
0231     if (qobject_cast<X11Window *>(workspace()->activeWindow()) == nullptr) {
0232         // Receiving Wayland selection not allowed when no Xwayland surface active
0233         // filter the event, but don't act upon it
0234         sendSelectionNotify(event, false);
0235         return true;
0236     }
0237 
0238     if (m_window != event->owner || !m_waylandSource) {
0239         if (event->time < m_timestamp) {
0240             // cancel earlier attempts at receiving a selection
0241             // TODO: is this for sure without problems?
0242             sendSelectionNotify(event, false);
0243             return true;
0244         }
0245         return false;
0246     }
0247     return m_waylandSource->handleSelectionRequest(event);
0248 }
0249 
0250 bool Selection::handleSelectionNotify(xcb_selection_notify_event_t *event)
0251 {
0252     if (m_xSource && m_xSource->handleSelectionNotify(event)) {
0253         return true;
0254     }
0255     for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
0256         if (transfer->handleSelectionNotify(event)) {
0257             return true;
0258         }
0259     }
0260     return false;
0261 }
0262 
0263 bool Selection::handlePropertyNotify(xcb_property_notify_event_t *event)
0264 {
0265     for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
0266         if (transfer->handlePropertyNotify(event)) {
0267             return true;
0268         }
0269     }
0270     for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) {
0271         if (transfer->handlePropertyNotify(event)) {
0272             return true;
0273         }
0274     }
0275     return false;
0276 }
0277 
0278 void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd)
0279 {
0280     // create new x to wl data transfer object
0281     auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSource->timestamp(), m_requestorWindow, this);
0282     m_xToWlTransfers << transfer;
0283 
0284     connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() {
0285         Q_EMIT transferFinished(transfer->timestamp());
0286         transfer->deleteLater();
0287         m_xToWlTransfers.removeOne(transfer);
0288         endTimeoutTransfersTimer();
0289     });
0290     startTimeoutTransfersTimer();
0291 }
0292 
0293 void Selection::startTransferToX(xcb_selection_request_event_t *event, qint32 fd)
0294 {
0295     // create new wl to x data transfer object
0296     auto *transfer = new TransferWltoX(m_atom, event, fd, this);
0297 
0298     connect(transfer, &TransferWltoX::selectionNotify, this, &Selection::sendSelectionNotify);
0299     connect(transfer, &TransferWltoX::finished, this, [this, transfer]() {
0300         Q_EMIT transferFinished(transfer->timestamp());
0301 
0302         // TODO: serialize? see comment below.
0303         //        const bool wasActive = (transfer == m_wlToXTransfers[0]);
0304         transfer->deleteLater();
0305         m_wlToXTransfers.removeOne(transfer);
0306         endTimeoutTransfersTimer();
0307         //        if (wasActive && !m_wlToXTransfers.isEmpty()) {
0308         //            m_wlToXTransfers[0]->startTransferFromSource();
0309         //        }
0310     });
0311 
0312     // add it to list of queued transfers
0313     m_wlToXTransfers.append(transfer);
0314 
0315     // TODO: Do we need to serialize the transfers, or can we do
0316     //       them in parallel as we do it right now?
0317     transfer->startTransferFromSource();
0318     //    if (m_wlToXTransfers.size() == 1) {
0319     //        transfer->startTransferFromSource();
0320     //    }
0321     startTimeoutTransfersTimer();
0322 }
0323 
0324 void Selection::startTimeoutTransfersTimer()
0325 {
0326     if (m_timeoutTransfers) {
0327         return;
0328     }
0329     m_timeoutTransfers = new QTimer(this);
0330     connect(m_timeoutTransfers, &QTimer::timeout, this, &Selection::timeoutTransfers);
0331     m_timeoutTransfers->start(5000);
0332 }
0333 
0334 void Selection::endTimeoutTransfersTimer()
0335 {
0336     if (m_xToWlTransfers.isEmpty() && m_wlToXTransfers.isEmpty()) {
0337         delete m_timeoutTransfers;
0338         m_timeoutTransfers = nullptr;
0339     }
0340 }
0341 
0342 void Selection::timeoutTransfers()
0343 {
0344     for (TransferXtoWl *transfer : std::as_const(m_xToWlTransfers)) {
0345         transfer->timeout();
0346     }
0347     for (TransferWltoX *transfer : std::as_const(m_wlToXTransfers)) {
0348         transfer->timeout();
0349     }
0350 }
0351 
0352 } // namespace Xwl
0353 } // namespace KWin