KWin
Loading...
Searching...
No Matches
selection_source.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "selection_source.h"
10#include "selection.h"
11#include "transfer.h"
12
13#include "atoms.h"
14#include "wayland/datadevice.h"
15#include "wayland/datasource.h"
16#include "wayland/seat.h"
17#include "wayland_server.h"
18
19#include <fcntl.h>
20#include <unistd.h>
21
22#include <xwayland_logging.h>
23
24namespace KWin
25{
26namespace Xwl
27{
28
30 : QObject(selection)
31 , m_selection(selection)
32 , m_window(selection->window())
33{
34}
35
37 : SelectionSource(selection)
38{
39}
40
42{
43 if (m_dsi == dsi) {
44 return;
45 }
46 for (const auto &mime : dsi->mimeTypes()) {
47 m_offers << mime;
48 }
49
50 // TODO, this can probably be removed after some testing
51 // all mime types should be constant after a data source is set
52 m_offerConnection = connect(dsi,
55
56 m_dsi = dsi;
57}
58
59void WlSource::receiveOffer(const QString &mime)
60{
61 m_offers << mime;
62}
63
64void WlSource::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
65{
66 Selection::sendSelectionNotify(event, success);
67}
68
69bool WlSource::handleSelectionRequest(xcb_selection_request_event_t *event)
70{
71 if (event->target == atoms->targets) {
72 sendTargets(event);
73 } else if (event->target == atoms->timestamp) {
74 sendTimestamp(event);
75 } else if (event->target == atoms->delete_atom) {
76 sendSelectionNotify(event, true);
77 } else {
78 // try to send mime data
79 if (!checkStartTransfer(event)) {
80 sendSelectionNotify(event, false);
81 }
82 }
83 return true;
84}
85
86void WlSource::sendTargets(xcb_selection_request_event_t *event)
87{
88 QList<xcb_atom_t> targets;
89 targets.resize(m_offers.size() + 2);
90 targets[0] = atoms->timestamp;
91 targets[1] = atoms->targets;
92
93 size_t cnt = 2;
94 for (const auto &mime : std::as_const(m_offers)) {
95 targets[cnt] = Selection::mimeTypeToAtom(mime);
96 cnt++;
97 }
98
99 xcb_change_property(kwinApp()->x11Connection(),
100 XCB_PROP_MODE_REPLACE,
101 event->requestor,
102 event->property,
103 XCB_ATOM_ATOM,
104 32, cnt, targets.data());
105 sendSelectionNotify(event, true);
106}
107
108void WlSource::sendTimestamp(xcb_selection_request_event_t *event)
109{
110 const xcb_timestamp_t time = timestamp();
111 xcb_change_property(kwinApp()->x11Connection(),
112 XCB_PROP_MODE_REPLACE,
113 event->requestor,
114 event->property,
115 XCB_ATOM_INTEGER,
116 32, 1, &time);
117
118 sendSelectionNotify(event, true);
119}
120
121bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event)
122{
123 // check interfaces available
124 if (!m_dsi) {
125 return false;
126 }
127
128 const auto targets = Selection::atomToMimeTypes(event->target);
129 if (targets.isEmpty()) {
130 qCDebug(KWIN_XWL) << "Unknown selection atom. Ignoring request.";
131 return false;
132 }
133 const auto firstTarget = targets[0];
134
135 auto cmp = [firstTarget](const QString &b) {
136 if (firstTarget == "text/uri-list") {
137 // Wayland sources might announce the old mime or the new standard
138 return firstTarget == b || b == "text/x-uri";
139 }
140 return firstTarget == b;
141 };
142 // check supported mimes
143 const auto offers = m_dsi->mimeTypes();
144 const auto mimeIt = std::find_if(offers.begin(), offers.end(), cmp);
145 if (mimeIt == offers.end()) {
146 // Requested Mime not supported. Not sending selection.
147 return false;
148 }
149
150 int p[2];
151 if (pipe2(p, O_CLOEXEC) == -1) {
152 qCWarning(KWIN_XWL) << "Pipe failed. Not sending selection.";
153 return false;
154 }
155
156 m_dsi->requestData(*mimeIt, p[1]);
157
158 Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]);
159 return true;
160}
161
162X11Source::X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event)
163 : SelectionSource(selection)
164{
165 setTimestamp(event->timestamp);
166}
167
169{
170 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
171 /* will lead to a selection request event for the new owner */
172 xcb_convert_selection(xcbConn,
173 window(),
174 selection()->atom(),
175 atoms->targets,
177 timestamp());
178 xcb_flush(xcbConn);
179}
180
181using Mime = QPair<QString, xcb_atom_t>;
182
183void X11Source::handleTargets()
184{
185 // receive targets
186 xcb_connection_t *xcbConn = kwinApp()->x11Connection();
187 xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
188 1,
189 window(),
191 XCB_GET_PROPERTY_TYPE_ANY,
192 0,
193 4096);
194 auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
195 if (!reply) {
196 qCDebug(KWIN_XWL) << "Failed to get selection property";
197 return;
198 }
199 if (reply->type != XCB_ATOM_ATOM) {
200 qCDebug(KWIN_XWL) << "Wrong reply type";
201 free(reply);
202 return;
203 }
204
205 QStringList added;
206 QStringList removed;
207
208 Mimes all;
209 xcb_atom_t *value = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
210 for (uint32_t i = 0; i < reply->value_len; i++) {
211 if (value[i] == XCB_ATOM_NONE) {
212 continue;
213 }
214
215 const auto mimeStrings = Selection::atomToMimeTypes(value[i]);
216 if (mimeStrings.isEmpty()) {
217 // TODO: this should never happen? assert?
218 continue;
219 }
220
221 const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
222 [value, i](const Mime &mime) {
223 return mime.second == value[i];
224 });
225
226 auto mimePair = Mime(mimeStrings[0], value[i]);
227 if (mimeIt == m_offers.end()) {
228 added << mimePair.first;
229 } else {
230 m_offers.removeAll(mimePair);
231 }
232 all << mimePair;
233 }
234 // all left in m_offers are not in the updated targets
235 for (const auto &mimePair : std::as_const(m_offers)) {
236 removed << mimePair.first;
237 }
238 m_offers = all;
239
240 if (!added.isEmpty() || !removed.isEmpty()) {
241 Q_EMIT offersChanged(added, removed);
242 }
243
244 free(reply);
245}
246
247void X11Source::setOffers(const Mimes &offers)
248{
249 m_offers = offers;
250}
251
252bool X11Source::handleSelectionNotify(xcb_selection_notify_event_t *event)
253{
254 if (event->requestor != window()) {
255 return false;
256 }
257 if (event->selection != selection()->atom()) {
258 return false;
259 }
260 if (event->property == XCB_ATOM_NONE) {
261 qCWarning(KWIN_XWL) << "Incoming X selection conversion failed";
262 return true;
263 }
264 if (event->target == atoms->targets) {
265 handleTargets();
266 return true;
267 }
268 return false;
269}
270
271void X11Source::startTransfer(const QString &mimeName, qint32 fd)
272{
273 const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(),
274 [mimeName](const Mime &mime) {
275 return mime.first == mimeName;
276 });
277 if (mimeIt == m_offers.end()) {
278 qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME.";
279 close(fd);
280 return;
281 }
282
283 Q_EMIT transferReady((*mimeIt).second, fd);
284}
285
286} // namespace Xwl
287} // namespace KWin
288
289#include "moc_selection_source.cpp"
The AbstractDataSource class abstracts the data that can be transferred to another client.
void mimeTypeOffered(const QString &)
virtual QStringList mimeTypes() const =0
virtual void requestData(const QString &mimeType, qint32 fd)=0
Xcb::Atom timestamp
Definition atoms.h:72
Xcb::Atom delete_atom
Definition atoms.h:74
Xcb::Atom wl_selection
Definition atoms.h:76
Xcb::Atom targets
Definition atoms.h:73
static void sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
static QStringList atomToMimeTypes(xcb_atom_t atom)
Definition selection.cpp:63
static xcb_atom_t mimeTypeToAtom(const QString &mimeType)
Definition selection.cpp:29
xcb_timestamp_t timestamp() const
SelectionSource(Selection *selection)
void setTimestamp(xcb_timestamp_t time)
xcb_window_t window() const
Selection * selection() const
WlSource(Selection *selection)
bool handleSelectionRequest(xcb_selection_request_event_t *event)
void sendTargets(xcb_selection_request_event_t *event)
void transferReady(xcb_selection_request_event_t *event, qint32 fd)
void sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
void receiveOffer(const QString &mime)
void sendTimestamp(xcb_selection_request_event_t *event)
void setDataSourceIface(AbstractDataSource *dsi)
bool handleSelectionNotify(xcb_selection_notify_event_t *event)
void offersChanged(const QStringList &added, const QStringList &removed)
X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event)
void transferReady(xcb_atom_t target, qint32 fd)
void startTransfer(const QString &mimeName, qint32 fd)
void setOffers(const Mimes &offers)
QPair< QString, xcb_atom_t > Mime
Definition drag_x.cpp:209
QList< QPair< QString, xcb_atom_t > > Mimes
Definition drag_x.h:31
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74