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_source.h"
0010 #include "selection.h"
0011 #include "transfer.h"
0012 
0013 #include "atoms.h"
0014 #include "wayland/datadevice_interface.h"
0015 #include "wayland/datasource_interface.h"
0016 #include "wayland/seat_interface.h"
0017 #include "wayland_server.h"
0018 
0019 #include <fcntl.h>
0020 #include <unistd.h>
0021 
0022 #include <xwayland_logging.h>
0023 
0024 namespace KWin
0025 {
0026 namespace Xwl
0027 {
0028 
0029 SelectionSource::SelectionSource(Selection *selection)
0030     : QObject(selection)
0031     , m_selection(selection)
0032     , m_window(selection->window())
0033 {
0034 }
0035 
0036 WlSource::WlSource(Selection *selection)
0037     : SelectionSource(selection)
0038 {
0039 }
0040 
0041 void WlSource::setDataSourceIface(KWaylandServer::AbstractDataSource *dsi)
0042 {
0043     if (m_dsi == dsi) {
0044         return;
0045     }
0046     for (const auto &mime : dsi->mimeTypes()) {
0047         m_offers << mime;
0048     }
0049 
0050     // TODO, this can probably be removed after some testing
0051     // all mime types should be constant after a data source is set
0052     m_offerConnection = connect(dsi,
0053                                 &KWaylandServer::DataSourceInterface::mimeTypeOffered,
0054                                 this, &WlSource::receiveOffer);
0055 
0056     m_dsi = dsi;
0057 }
0058 
0059 void WlSource::receiveOffer(const QString &mime)
0060 {
0061     m_offers << mime;
0062 }
0063 
0064 void WlSource::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
0065 {
0066     Selection::sendSelectionNotify(event, success);
0067 }
0068 
0069 bool WlSource::handleSelectionRequest(xcb_selection_request_event_t *event)
0070 {
0071     if (event->target == atoms->targets) {
0072         sendTargets(event);
0073     } else if (event->target == atoms->timestamp) {
0074         sendTimestamp(event);
0075     } else if (event->target == atoms->delete_atom) {
0076         sendSelectionNotify(event, true);
0077     } else {
0078         // try to send mime data
0079         if (!checkStartTransfer(event)) {
0080             sendSelectionNotify(event, false);
0081         }
0082     }
0083     return true;
0084 }
0085 
0086 void WlSource::sendTargets(xcb_selection_request_event_t *event)
0087 {
0088     QVector<xcb_atom_t> targets;
0089     targets.resize(m_offers.size() + 2);
0090     targets[0] = atoms->timestamp;
0091     targets[1] = atoms->targets;
0092 
0093     size_t cnt = 2;
0094     for (const auto &mime : std::as_const(m_offers)) {
0095         targets[cnt] = Selection::mimeTypeToAtom(mime);
0096         cnt++;
0097     }
0098 
0099     xcb_change_property(kwinApp()->x11Connection(),
0100                         XCB_PROP_MODE_REPLACE,
0101                         event->requestor,
0102                         event->property,
0103                         XCB_ATOM_ATOM,
0104                         32, cnt, targets.data());
0105     sendSelectionNotify(event, true);
0106 }
0107 
0108 void WlSource::sendTimestamp(xcb_selection_request_event_t *event)
0109 {
0110     const xcb_timestamp_t time = timestamp();
0111     xcb_change_property(kwinApp()->x11Connection(),
0112                         XCB_PROP_MODE_REPLACE,
0113                         event->requestor,
0114                         event->property,
0115                         XCB_ATOM_INTEGER,
0116                         32, 1, &time);
0117 
0118     sendSelectionNotify(event, true);
0119 }
0120 
0121 bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event)
0122 {
0123     // check interfaces available
0124     if (!m_dsi) {
0125         return false;
0126     }
0127 
0128     const auto targets = Selection::atomToMimeTypes(event->target);
0129     if (targets.isEmpty()) {
0130         qCDebug(KWIN_XWL) << "Unknown selection atom. Ignoring request.";
0131         return false;
0132     }
0133     const auto firstTarget = targets[0];
0134 
0135     auto cmp = [firstTarget](const QString &b) {
0136         if (firstTarget == "text/uri-list") {
0137             // Wayland sources might announce the old mime or the new standard
0138             return firstTarget == b || b == "text/x-uri";
0139         }
0140         return firstTarget == b;
0141     };
0142     // check supported mimes
0143     const auto offers = m_dsi->mimeTypes();
0144     const auto mimeIt = std::find_if(offers.begin(), offers.end(), cmp);
0145     if (mimeIt == offers.end()) {
0146         // Requested Mime not supported. Not sending selection.
0147         return false;
0148     }
0149 
0150     int p[2];
0151     if (pipe2(p, O_CLOEXEC) == -1) {
0152         qCWarning(KWIN_XWL) << "Pipe failed. Not sending selection.";
0153         return false;
0154     }
0155 
0156     m_dsi->requestData(*mimeIt, p[1]);
0157 
0158     Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]);
0159     return true;
0160 }
0161 
0162 X11Source::X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event)
0163     : SelectionSource(selection)
0164     , m_owner(event->owner)
0165 {
0166     setTimestamp(event->timestamp);
0167 }
0168 
0169 void X11Source::getTargets()
0170 {
0171     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0172     /* will lead to a selection request event for the new owner */
0173     xcb_convert_selection(xcbConn,
0174                           window(),
0175                           selection()->atom(),
0176                           atoms->targets,
0177                           atoms->wl_selection,
0178                           timestamp());
0179     xcb_flush(xcbConn);
0180 }
0181 
0182 using Mime = QPair<QString, xcb_atom_t>;
0183 
0184 void X11Source::handleTargets()
0185 {
0186     // receive targets
0187     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0188     xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
0189                                                         1,
0190                                                         window(),
0191                                                         atoms->wl_selection,
0192                                                         XCB_GET_PROPERTY_TYPE_ANY,
0193                                                         0,
0194                                                         4096);
0195     auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
0196     if (!reply) {
0197         qCDebug(KWIN_XWL) << "Failed to get selection property";
0198         return;
0199     }
0200     if (reply->type != XCB_ATOM_ATOM) {
0201         qCDebug(KWIN_XWL) << "Wrong reply type";
0202         free(reply);
0203         return;
0204     }
0205 
0206     QStringList added;
0207     QStringList removed;
0208 
0209     Mimes all;
0210     xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
0211     for (uint32_t i = 0; i < reply->value_len; i++) {
0212         if (value[i] == XCB_ATOM_NONE) {
0213             continue;
0214         }
0215 
0216         const auto mimeStrings = Selection::atomToMimeTypes(value[i]);
0217         if (mimeStrings.isEmpty()) {
0218             // TODO: this should never happen? assert?
0219             continue;
0220         }
0221 
0222         const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
0223                                          [value, i](const Mime &mime) {
0224                                              return mime.second == value[i];
0225                                          });
0226 
0227         auto mimePair = Mime(mimeStrings[0], value[i]);
0228         if (mimeIt == m_offers.end()) {
0229             added << mimePair.first;
0230         } else {
0231             m_offers.removeAll(mimePair);
0232         }
0233         all << mimePair;
0234     }
0235     // all left in m_offers are not in the updated targets
0236     for (const auto &mimePair : std::as_const(m_offers)) {
0237         removed << mimePair.first;
0238     }
0239     m_offers = all;
0240 
0241     if (!added.isEmpty() || !removed.isEmpty()) {
0242         Q_EMIT offersChanged(added, removed);
0243     }
0244 
0245     free(reply);
0246 }
0247 
0248 void X11Source::setOffers(const Mimes &offers)
0249 {
0250     m_offers = offers;
0251 }
0252 
0253 bool X11Source::handleSelectionNotify(xcb_selection_notify_event_t *event)
0254 {
0255     if (event->requestor != window()) {
0256         return false;
0257     }
0258     if (event->selection != selection()->atom()) {
0259         return false;
0260     }
0261     if (event->property == XCB_ATOM_NONE) {
0262         qCWarning(KWIN_XWL) << "Incoming X selection conversion failed";
0263         return true;
0264     }
0265     if (event->target == atoms->targets) {
0266         handleTargets();
0267         return true;
0268     }
0269     return false;
0270 }
0271 
0272 void X11Source::startTransfer(const QString &mimeName, qint32 fd)
0273 {
0274     const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
0275                                      [mimeName](const Mime &mime) {
0276                                          return mime.first == mimeName;
0277                                      });
0278     if (mimeIt == m_offers.end()) {
0279         qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME.";
0280         close(fd);
0281         return;
0282     }
0283 
0284     Q_EMIT transferReady((*mimeIt).second, fd);
0285 }
0286 
0287 } // namespace Xwl
0288 } // namespace KWin