Changeset View
Changeset View
Standalone View
Standalone View
xwl/selection_source.cpp
- This file was added.
1 | /******************************************************************** | ||||
---|---|---|---|---|---|
2 | KWin - the KDE window manager | ||||
3 | This file is part of the KDE project. | ||||
4 | | ||||
5 | Copyright 2019 Roman Gilg <subdiff@gmail.com> | ||||
6 | | ||||
7 | This program is free software; you can redistribute it and/or modify | ||||
8 | it under the terms of the GNU General Public License as published by | ||||
9 | the Free Software Foundation; either version 2 of the License, or | ||||
10 | (at your option) any later version. | ||||
11 | | ||||
12 | This program is distributed in the hope that it will be useful, | ||||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
15 | GNU General Public License for more details. | ||||
16 | | ||||
17 | You should have received a copy of the GNU General Public License | ||||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
19 | *********************************************************************/ | ||||
20 | #include "selection_source.h" | ||||
21 | #include "selection.h" | ||||
22 | #include "transfer.h" | ||||
23 | | ||||
24 | #include "atoms.h" | ||||
25 | #include "wayland_server.h" | ||||
26 | | ||||
27 | #include <KWayland/Client/connection_thread.h> | ||||
28 | #include <KWayland/Client/datadevicemanager.h> | ||||
29 | #include <KWayland/Client/datadevice.h> | ||||
30 | #include <KWayland/Client/datasource.h> | ||||
31 | | ||||
32 | #include <KWayland/Server/seat_interface.h> | ||||
33 | #include <KWayland/Server/datadevice_interface.h> | ||||
34 | #include <KWayland/Server/datasource_interface.h> | ||||
35 | | ||||
36 | #include <unistd.h> | ||||
37 | | ||||
38 | #include <xwayland_logging.h> | ||||
39 | | ||||
40 | namespace KWin { | ||||
41 | namespace Xwl { | ||||
42 | | ||||
43 | SelectionSource::SelectionSource(Selection *sel) | ||||
44 | : QObject(sel), | ||||
45 | m_sel(sel) | ||||
46 | { | ||||
47 | } | ||||
48 | | ||||
49 | WlSource::WlSource(Selection *sel, KWayland::Server::DataDeviceInterface *ddi) | ||||
50 | : SelectionSource(sel), | ||||
51 | m_ddi(ddi) | ||||
52 | { | ||||
53 | Q_ASSERT(ddi); | ||||
54 | } | ||||
55 | | ||||
56 | void WlSource::setDataSourceIface(KWayland::Server::DataSourceInterface *dsi) | ||||
57 | { | ||||
58 | if (m_dsi == dsi) { | ||||
59 | return; | ||||
60 | } | ||||
61 | for (const auto &mime : dsi->mimeTypes()) { | ||||
62 | m_offers << mime; | ||||
63 | } | ||||
64 | m_offerCon = connect(dsi, | ||||
65 | &KWayland::Server::DataSourceInterface::mimeTypeOffered, | ||||
66 | this, &WlSource::receiveOffer); | ||||
67 | m_dsi = dsi; | ||||
68 | } | ||||
69 | | ||||
70 | void WlSource::receiveOffer(const QString &mime) | ||||
71 | { | ||||
72 | m_offers << mime; | ||||
73 | } | ||||
74 | | ||||
75 | void WlSource::sendSelNotify(xcb_selection_request_event_t *event, bool success) | ||||
76 | { | ||||
77 | selection()->sendSelNotify(event, success); | ||||
78 | } | ||||
79 | | ||||
80 | bool WlSource::handleSelRequest(xcb_selection_request_event_t *event) | ||||
81 | { | ||||
82 | if (event->target == atoms->targets) { | ||||
83 | sendTargets(event); | ||||
84 | } else if (event->target == atoms->timestamp) { | ||||
85 | sendTimestamp(event); | ||||
86 | } else if (event->target == atoms->delete_atom) { | ||||
87 | sendSelNotify(event, true); | ||||
88 | } else { | ||||
89 | // try to send mime data | ||||
90 | if (!checkStartTransfer(event)) { | ||||
91 | sendSelNotify(event, false); | ||||
92 | } | ||||
93 | } | ||||
94 | return true; | ||||
95 | } | ||||
96 | | ||||
97 | void WlSource::sendTargets(xcb_selection_request_event_t *event) | ||||
98 | { | ||||
99 | QVector<xcb_atom_t> targets; | ||||
100 | targets.resize(m_offers.size() + 2); | ||||
101 | targets[0] = atoms->timestamp; | ||||
102 | targets[1] = atoms->targets; | ||||
103 | | ||||
104 | size_t cnt = 2; | ||||
105 | for (const auto mime : m_offers) { | ||||
106 | targets[cnt] = Selection::mimeTypeToAtom(mime); | ||||
107 | cnt++; | ||||
108 | } | ||||
109 | | ||||
110 | xcb_change_property(kwinApp()->x11Connection(), | ||||
111 | XCB_PROP_MODE_REPLACE, | ||||
112 | event->requestor, | ||||
113 | event->property, | ||||
114 | XCB_ATOM_ATOM, | ||||
115 | 32, cnt, targets.data()); | ||||
116 | sendSelNotify(event, true); | ||||
117 | } | ||||
118 | | ||||
119 | void WlSource::sendTimestamp(xcb_selection_request_event_t *event) | ||||
120 | { | ||||
121 | const xcb_timestamp_t time = timestamp(); | ||||
122 | xcb_change_property(kwinApp()->x11Connection(), | ||||
123 | XCB_PROP_MODE_REPLACE, | ||||
124 | event->requestor, | ||||
125 | event->property, | ||||
126 | XCB_ATOM_INTEGER, | ||||
127 | 32, 1, &time); | ||||
128 | | ||||
129 | sendSelNotify(event, true); | ||||
130 | } | ||||
131 | | ||||
132 | bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event) | ||||
133 | { | ||||
134 | // check interfaces available | ||||
135 | if (!m_ddi || !m_dsi) { | ||||
136 | return false; | ||||
137 | } | ||||
138 | | ||||
139 | const auto targets = Selection::atomToMimeTypes(event->target); | ||||
140 | if (targets.isEmpty()) { | ||||
141 | qCDebug(KWIN_XWL) << "Unknown selection atom. Ignoring request."; | ||||
142 | return false; | ||||
143 | } | ||||
144 | const auto firstTarget = targets[0]; | ||||
145 | | ||||
146 | auto cmp = [firstTarget](const QString &b) { | ||||
147 | if (firstTarget == "text/uri-list") { | ||||
148 | // Wayland sources might announce the old mime or the new standard | ||||
149 | return firstTarget == b || b == "text/x-uri"; | ||||
150 | } | ||||
151 | return firstTarget == b; | ||||
152 | }; | ||||
153 | // check supported mimes | ||||
154 | const auto offers = m_dsi->mimeTypes(); | ||||
155 | const auto mimeIt = std::find_if(offers.begin(), offers.end(), cmp); | ||||
156 | if (mimeIt == offers.end()) { | ||||
157 | // Requested Mime not supported. Not sending selection. | ||||
158 | return false; | ||||
159 | } | ||||
160 | | ||||
161 | int p[2]; | ||||
162 | if (pipe(p) == -1) { | ||||
163 | qCWarning(KWIN_XWL) << "Pipe failed. Not sending selection."; | ||||
164 | return false; | ||||
165 | } | ||||
166 | | ||||
167 | m_dsi->requestData(*mimeIt, p[1]); | ||||
168 | waylandServer()->dispatch(); | ||||
169 | | ||||
170 | Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]); | ||||
171 | return true; | ||||
172 | } | ||||
173 | | ||||
174 | X11Source::X11Source(Selection *sel, xcb_xfixes_selection_notify_event_t *event) | ||||
175 | : SelectionSource(sel), | ||||
176 | m_owner(event->owner) | ||||
177 | { | ||||
178 | setTimestamp(event->timestamp); | ||||
179 | } | ||||
180 | | ||||
181 | void X11Source::getTargets() | ||||
182 | { | ||||
183 | auto *xcbConn = kwinApp()->x11Connection(); | ||||
184 | /* will lead to a selection request event for the new owner */ | ||||
185 | xcb_convert_selection(xcbConn, | ||||
186 | selection()->window(), | ||||
187 | selection()->atom(), | ||||
188 | atoms->targets, | ||||
189 | atoms->wl_selection, | ||||
190 | timestamp()); | ||||
191 | xcb_flush(xcbConn); | ||||
192 | } | ||||
193 | | ||||
194 | using Mime = QPair<QString, xcb_atom_t>; | ||||
195 | | ||||
196 | void X11Source::handleTargets() | ||||
197 | { | ||||
198 | // receive targets | ||||
199 | auto *xcbConn = kwinApp()->x11Connection(); | ||||
200 | xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn, | ||||
201 | 1, | ||||
202 | selection()->window(), | ||||
203 | atoms->wl_selection, | ||||
204 | XCB_GET_PROPERTY_TYPE_ANY, | ||||
205 | 0, | ||||
206 | 4096 | ||||
207 | ); | ||||
208 | auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL); | ||||
209 | if (reply == NULL) { | ||||
210 | return; | ||||
211 | } | ||||
212 | if (reply->type != XCB_ATOM_ATOM) { | ||||
213 | free(reply); | ||||
214 | return; | ||||
215 | } | ||||
216 | | ||||
217 | Mimes all; | ||||
218 | QVector<QString> add, rm; | ||||
219 | xcb_atom_t *value = static_cast<xcb_atom_t*>(xcb_get_property_value(reply)); | ||||
220 | for (uint32_t i = 0; i < reply->value_len; i++) { | ||||
221 | if (value[i] == XCB_ATOM_NONE) { | ||||
222 | continue; | ||||
223 | } | ||||
224 | | ||||
225 | const auto mimeStrings = Selection::atomToMimeTypes(value[i]); | ||||
226 | if (mimeStrings.isEmpty()) { | ||||
227 | // TODO: this should never happen? assert? | ||||
228 | continue; | ||||
229 | } | ||||
230 | | ||||
231 | | ||||
232 | const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(), | ||||
233 | [value, i](const Mime &m) | ||||
234 | { return m.second == value[i]; }); | ||||
235 | | ||||
236 | auto mimePair = Mime(mimeStrings[0], value[i]); | ||||
237 | if (mimeIt == m_offers.end()) { | ||||
238 | add << mimePair.first; | ||||
239 | } else { | ||||
240 | m_offers.removeAll(mimePair); | ||||
241 | } | ||||
242 | all << mimePair; | ||||
243 | } | ||||
244 | // all left in m_offers are not in the updated targets | ||||
245 | for (const auto mimePair : m_offers) { | ||||
246 | rm << mimePair.first; | ||||
247 | } | ||||
248 | m_offers = all; | ||||
249 | | ||||
250 | if (!add.isEmpty() || !rm.isEmpty()) { | ||||
251 | Q_EMIT offersChanged(add, rm); | ||||
252 | } | ||||
253 | | ||||
254 | free(reply); | ||||
255 | } | ||||
256 | | ||||
257 | void X11Source::setDataSource(KWayland::Client::DataSource *ds) | ||||
258 | { | ||||
259 | Q_ASSERT(ds); | ||||
260 | if (m_ds) { | ||||
261 | delete m_ds; | ||||
262 | } | ||||
263 | m_ds = ds; | ||||
264 | | ||||
265 | std::for_each(m_offers.begin(), m_offers.end(), | ||||
266 | [ds](const Mime &offer){ | ||||
267 | ds->offer(offer.first); | ||||
268 | }); | ||||
269 | connect(ds, &KWayland::Client::DataSource::sendDataRequested, | ||||
270 | this, &X11Source::startTransfer); | ||||
271 | } | ||||
272 | | ||||
273 | void X11Source::setOffers(const Mimes &offers) | ||||
274 | { | ||||
275 | // TODO: share code with handleTargets and emit signals accordingly? | ||||
276 | m_offers = offers; | ||||
277 | } | ||||
278 | | ||||
279 | bool X11Source::handleSelNotify(xcb_selection_notify_event_t *event) | ||||
280 | { | ||||
281 | if (event->requestor != selection()->window()) { | ||||
282 | return false; | ||||
283 | } | ||||
284 | if (event->selection != selection()->atom()) { | ||||
285 | return false; | ||||
286 | } | ||||
287 | if (event->property == XCB_ATOM_NONE) { | ||||
288 | qCWarning(KWIN_XWL) << "Incoming X selection conversion failed"; | ||||
289 | return true; | ||||
290 | } | ||||
291 | if (event->target == atoms->targets) { | ||||
292 | handleTargets(); | ||||
293 | return true; | ||||
294 | } | ||||
295 | return false; | ||||
296 | } | ||||
297 | | ||||
298 | void X11Source::startTransfer(const QString &mimeName, qint32 fd) | ||||
299 | { | ||||
300 | const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(), | ||||
301 | [mimeName](const Mime &m) | ||||
302 | { return m.first == mimeName; }); | ||||
303 | if (mimeIt == m_offers.end()) { | ||||
304 | qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME."; | ||||
305 | close(fd); | ||||
306 | return; | ||||
307 | } | ||||
308 | | ||||
309 | Q_EMIT transferReady((*mimeIt).second, fd); | ||||
310 | } | ||||
311 | | ||||
312 | | ||||
313 | } | ||||
314 | } |