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

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.h"
0015 #include "wayland/datasource.h"
0016 #include "wayland/seat.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(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                                 &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     QList<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 {
0165     setTimestamp(event->timestamp);
0166 }
0167 
0168 void X11Source::getTargets()
0169 {
0170     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0171     /* will lead to a selection request event for the new owner */
0172     xcb_convert_selection(xcbConn,
0173                           window(),
0174                           selection()->atom(),
0175                           atoms->targets,
0176                           atoms->wl_selection,
0177                           timestamp());
0178     xcb_flush(xcbConn);
0179 }
0180 
0181 using Mime = QPair<QString, xcb_atom_t>;
0182 
0183 void X11Source::handleTargets()
0184 {
0185     // receive targets
0186     xcb_connection_t *xcbConn = kwinApp()->x11Connection();
0187     xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
0188                                                         1,
0189                                                         window(),
0190                                                         atoms->wl_selection,
0191                                                         XCB_GET_PROPERTY_TYPE_ANY,
0192                                                         0,
0193                                                         4096);
0194     auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
0195     if (!reply) {
0196         qCDebug(KWIN_XWL) << "Failed to get selection property";
0197         return;
0198     }
0199     if (reply->type != XCB_ATOM_ATOM) {
0200         qCDebug(KWIN_XWL) << "Wrong reply type";
0201         free(reply);
0202         return;
0203     }
0204 
0205     QStringList added;
0206     QStringList removed;
0207 
0208     Mimes all;
0209     xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
0210     for (uint32_t i = 0; i < reply->value_len; i++) {
0211         if (value[i] == XCB_ATOM_NONE) {
0212             continue;
0213         }
0214 
0215         const auto mimeStrings = Selection::atomToMimeTypes(value[i]);
0216         if (mimeStrings.isEmpty()) {
0217             // TODO: this should never happen? assert?
0218             continue;
0219         }
0220 
0221         const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
0222                                          [value, i](const Mime &mime) {
0223                                              return mime.second == value[i];
0224                                          });
0225 
0226         auto mimePair = Mime(mimeStrings[0], value[i]);
0227         if (mimeIt == m_offers.end()) {
0228             added << mimePair.first;
0229         } else {
0230             m_offers.removeAll(mimePair);
0231         }
0232         all << mimePair;
0233     }
0234     // all left in m_offers are not in the updated targets
0235     for (const auto &mimePair : std::as_const(m_offers)) {
0236         removed << mimePair.first;
0237     }
0238     m_offers = all;
0239 
0240     if (!added.isEmpty() || !removed.isEmpty()) {
0241         Q_EMIT offersChanged(added, removed);
0242     }
0243 
0244     free(reply);
0245 }
0246 
0247 void X11Source::setOffers(const Mimes &offers)
0248 {
0249     m_offers = offers;
0250 }
0251 
0252 bool X11Source::handleSelectionNotify(xcb_selection_notify_event_t *event)
0253 {
0254     if (event->requestor != window()) {
0255         return false;
0256     }
0257     if (event->selection != selection()->atom()) {
0258         return false;
0259     }
0260     if (event->property == XCB_ATOM_NONE) {
0261         qCWarning(KWIN_XWL) << "Incoming X selection conversion failed";
0262         return true;
0263     }
0264     if (event->target == atoms->targets) {
0265         handleTargets();
0266         return true;
0267     }
0268     return false;
0269 }
0270 
0271 void X11Source::startTransfer(const QString &mimeName, qint32 fd)
0272 {
0273     const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
0274                                      [mimeName](const Mime &mime) {
0275                                          return mime.first == mimeName;
0276                                      });
0277     if (mimeIt == m_offers.end()) {
0278         qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME.";
0279         close(fd);
0280         return;
0281     }
0282 
0283     Q_EMIT transferReady((*mimeIt).second, fd);
0284 }
0285 
0286 } // namespace Xwl
0287 } // namespace KWin
0288 
0289 #include "moc_selection_source.cpp"