File indexing completed on 2025-03-16 11:21:58
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