1/*
2 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "kselectionowner.h"
8
9#include "kwindowsystem.h"
10#include "kxcbevent_p.h"
11#include <config-kwindowsystem.h>
12
13#include <QAbstractNativeEventFilter>
14#include <QBasicTimer>
15#include <QDebug>
16#include <QGuiApplication>
17#include <QTimerEvent>
18
19#include <private/qtx11extras_p.h>
20
21static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection)
22{
23 xcb_window_t owner = XCB_NONE;
24 xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, cookie: xcb_get_selection_owner(c, selection), e: nullptr);
25
26 if (reply) {
27 owner = reply->owner;
28 free(ptr: reply);
29 }
30
31 return owner;
32}
33
34static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name)
35{
36 xcb_atom_t atom = XCB_NONE;
37 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie: xcb_intern_atom(c, only_if_exists: false, name_len: strlen(s: name), name), e: nullptr);
38
39 if (reply) {
40 atom = reply->atom;
41 free(ptr: reply);
42 }
43
44 return atom;
45}
46
47class Q_DECL_HIDDEN KSelectionOwner::Private : public QAbstractNativeEventFilter
48{
49public:
50 enum State { Idle, WaitingForTimestamp, WaitingForPreviousOwner };
51
52 Private(KSelectionOwner *owner_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
53 : state(Idle)
54 , selection(selection_P)
55 , connection(c)
56 , root(root)
57 , window(XCB_NONE)
58 , prev_owner(XCB_NONE)
59 , timestamp(XCB_CURRENT_TIME)
60 , extra1(0)
61 , extra2(0)
62 , force_kill(false)
63 , owner(owner_P)
64 {
65 QCoreApplication::instance()->installNativeEventFilter(filterObj: this);
66 }
67
68 void claimSucceeded();
69 void gotTimestamp();
70 void timeout();
71
72 State state;
73 const xcb_atom_t selection;
74 xcb_connection_t *connection;
75 xcb_window_t root;
76 xcb_window_t window;
77 xcb_window_t prev_owner;
78 xcb_timestamp_t timestamp;
79 uint32_t extra1, extra2;
80 QBasicTimer timer;
81 bool force_kill;
82 static xcb_atom_t manager_atom;
83 static xcb_atom_t xa_multiple;
84 static xcb_atom_t xa_targets;
85 static xcb_atom_t xa_timestamp;
86
87 static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P);
88 static Private *create(KSelectionOwner *owner, const char *selection_P, int screen_P);
89 static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root);
90 static Private *create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root);
91
92protected:
93 bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
94 {
95 if (eventType != "xcb_generic_event_t") {
96 return false;
97 }
98 return owner->filterEvent(ev_P: message);
99 }
100
101private:
102 KSelectionOwner *owner;
103};
104
105KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P)
106{
107 if (KWindowSystem::isPlatformX11()) {
108 return create(owner, selection_P, c: QX11Info::connection(), root: QX11Info::appRootWindow(screen: screen_P));
109 }
110 qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug.";
111 return nullptr;
112}
113
114KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
115{
116 return new Private(owner, selection_P, c, root);
117}
118
119KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, int screen_P)
120{
121 if (KWindowSystem::isPlatformX11()) {
122 return create(owner, selection_P, c: QX11Info::connection(), root: QX11Info::appRootWindow(screen: screen_P));
123 }
124 qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug.";
125 return nullptr;
126}
127
128KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root)
129{
130 return new Private(owner, intern_atom(c, name: selection_P), c, root);
131}
132
133KSelectionOwner::KSelectionOwner(xcb_atom_t selection_P, int screen_P, QObject *parent_P)
134 : QObject(parent_P)
135 , d(Private::create(owner: this, selection_P, screen_P))
136{
137}
138
139KSelectionOwner::KSelectionOwner(const char *selection_P, int screen_P, QObject *parent_P)
140 : QObject(parent_P)
141 , d(Private::create(owner: this, selection_P, screen_P))
142{
143}
144
145KSelectionOwner::KSelectionOwner(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
146 : QObject(parent)
147 , d(Private::create(owner: this, selection_P: selection, c, root))
148{
149}
150
151KSelectionOwner::KSelectionOwner(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
152 : QObject(parent)
153 , d(Private::create(owner: this, selection_P: selection, c, root))
154{
155}
156
157KSelectionOwner::~KSelectionOwner()
158{
159 if (d) {
160 release();
161 if (d->window != XCB_WINDOW_NONE) {
162 xcb_destroy_window(c: d->connection, window: d->window); // also makes the selection not owned
163 }
164 delete d;
165 }
166}
167
168void KSelectionOwner::Private::claimSucceeded()
169{
170 state = Idle;
171
172 KXcbEvent<xcb_client_message_event_t> ev;
173 ev.response_type = XCB_CLIENT_MESSAGE;
174 ev.format = 32;
175 ev.window = root;
176 ev.type = Private::manager_atom;
177 ev.data.data32[0] = timestamp;
178 ev.data.data32[1] = selection;
179 ev.data.data32[2] = window;
180 ev.data.data32[3] = extra1;
181 ev.data.data32[4] = extra2;
182
183 xcb_send_event(c: connection, propagate: false, destination: root, event_mask: XCB_EVENT_MASK_STRUCTURE_NOTIFY, event: ev.buffer());
184
185 // qDebug() << "Claimed selection";
186
187 Q_EMIT owner->claimedOwnership();
188}
189
190void KSelectionOwner::Private::gotTimestamp()
191{
192 Q_ASSERT(state == WaitingForTimestamp);
193
194 state = Idle;
195
196 xcb_connection_t *c = connection;
197
198 // Set the selection owner and immediately verify that the claim was successful
199 xcb_set_selection_owner(c, owner: window, selection, time: timestamp);
200 xcb_window_t new_owner = get_selection_owner(c, selection);
201
202 if (new_owner != window) {
203 // qDebug() << "Failed to claim selection : " << new_owner;
204 xcb_destroy_window(c, window);
205 timestamp = XCB_CURRENT_TIME;
206 window = XCB_NONE;
207
208 Q_EMIT owner->failedToClaimOwnership();
209 return;
210 }
211
212 if (prev_owner != XCB_NONE && force_kill) {
213 // qDebug() << "Waiting for previous owner to disown";
214 timer.start(msec: 1000, obj: owner);
215 state = WaitingForPreviousOwner;
216
217 // Note: We've already selected for structure notify events
218 // on the previous owner window
219 } else {
220 // If there was no previous owner, we're done
221 claimSucceeded();
222 }
223}
224
225void KSelectionOwner::Private::timeout()
226{
227 Q_ASSERT(state == WaitingForPreviousOwner);
228
229 state = Idle;
230
231 if (force_kill) {
232 // qDebug() << "Killing previous owner";
233 xcb_connection_t *c = connection;
234
235 // Ignore any errors from the kill request
236 xcb_generic_error_t *err = xcb_request_check(c, cookie: xcb_kill_client_checked(c, resource: prev_owner));
237 free(ptr: err);
238
239 claimSucceeded();
240 } else {
241 Q_EMIT owner->failedToClaimOwnership();
242 }
243}
244
245void KSelectionOwner::claim(bool force_P, bool force_kill_P)
246{
247 if (!d) {
248 return;
249 }
250 Q_ASSERT(d->state == Private::Idle);
251
252 if (Private::manager_atom == XCB_NONE) {
253 getAtoms();
254 }
255
256 if (d->timestamp != XCB_CURRENT_TIME) {
257 release();
258 }
259
260 xcb_connection_t *c = d->connection;
261 d->prev_owner = get_selection_owner(c, selection: d->selection);
262
263 if (d->prev_owner != XCB_NONE) {
264 if (!force_P) {
265 // qDebug() << "Selection already owned, failing";
266 Q_EMIT failedToClaimOwnership();
267 return;
268 }
269
270 // Select structure notify events so get an event when the previous owner
271 // destroys the window
272 uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
273 xcb_change_window_attributes(c, window: d->prev_owner, value_mask: XCB_CW_EVENT_MASK, value_list: &mask);
274 }
275
276 uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
277
278 d->window = xcb_generate_id(c);
279 xcb_create_window(c,
280 XCB_COPY_FROM_PARENT,
281 wid: d->window,
282 parent: d->root,
283 x: 0,
284 y: 0,
285 width: 1,
286 height: 1,
287 border_width: 0,
288 class: XCB_WINDOW_CLASS_INPUT_ONLY,
289 XCB_COPY_FROM_PARENT,
290 value_mask: XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
291 value_list: values);
292
293 // Trigger a property change event so we get a timestamp
294 xcb_atom_t tmp = XCB_ATOM_ATOM;
295 xcb_change_property(c, mode: XCB_PROP_MODE_REPLACE, window: d->window, property: XCB_ATOM_ATOM, type: XCB_ATOM_ATOM, format: 32, data_len: 1, data: (const void *)&tmp);
296
297 // Now we have to return to the event loop and wait for the property change event
298 d->force_kill = force_kill_P;
299 d->state = Private::WaitingForTimestamp;
300}
301
302// destroy resource first
303void KSelectionOwner::release()
304{
305 if (!d) {
306 return;
307 }
308 if (d->timestamp == XCB_CURRENT_TIME) {
309 return;
310 }
311
312 xcb_destroy_window(c: d->connection, window: d->window); // also makes the selection not owned
313 d->window = XCB_NONE;
314
315 // qDebug() << "Releasing selection";
316
317 d->timestamp = XCB_CURRENT_TIME;
318}
319
320xcb_window_t KSelectionOwner::ownerWindow() const
321{
322 if (!d) {
323 return XCB_WINDOW_NONE;
324 }
325 if (d->timestamp == XCB_CURRENT_TIME) {
326 return XCB_NONE;
327 }
328
329 return d->window;
330}
331
332void KSelectionOwner::setData(uint32_t extra1_P, uint32_t extra2_P)
333{
334 if (!d) {
335 return;
336 }
337 d->extra1 = extra1_P;
338 d->extra2 = extra2_P;
339}
340
341bool KSelectionOwner::filterEvent(void *ev_P)
342{
343 if (!d) {
344 return false;
345 }
346 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P);
347 const uint response_type = event->response_type & ~0x80;
348
349#if 0
350 // There's no generic way to get the window for an event in xcb, it depends on the type of event
351 // This handleMessage virtual doesn't seem used anyway.
352 if (d->timestamp != CurrentTime && ev_P->xany.window == d->window) {
353 if (handleMessage(ev_P)) {
354 return true;
355 }
356 }
357#endif
358 switch (response_type) {
359 case XCB_SELECTION_CLEAR: {
360 xcb_selection_clear_event_t *ev = reinterpret_cast<xcb_selection_clear_event_t *>(event);
361 if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
362 return false;
363 }
364
365 d->timestamp = XCB_CURRENT_TIME;
366 // qDebug() << "Lost selection";
367
368 xcb_window_t window = d->window;
369 Q_EMIT lostOwnership();
370
371 // Unset the event mask before we destroy the window so we don't get a destroy event
372 uint32_t event_mask = XCB_NONE;
373 xcb_change_window_attributes(c: d->connection, window, value_mask: XCB_CW_EVENT_MASK, value_list: &event_mask);
374 xcb_destroy_window(c: d->connection, window);
375 return true;
376 }
377 case XCB_DESTROY_NOTIFY: {
378 xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
379 if (ev->window == d->prev_owner) {
380 if (d->state == Private::WaitingForPreviousOwner) {
381 d->timer.stop();
382 d->claimSucceeded();
383 return true;
384 }
385 // It is possible for the previous owner to be destroyed
386 // while we're waiting for the timestamp
387 d->prev_owner = XCB_NONE;
388 }
389
390 if (d->timestamp == XCB_CURRENT_TIME || ev->window != d->window) {
391 return false;
392 }
393
394 d->timestamp = XCB_CURRENT_TIME;
395 // qDebug() << "Lost selection (destroyed)";
396 Q_EMIT lostOwnership();
397 return true;
398 }
399 case XCB_SELECTION_NOTIFY: {
400 xcb_selection_notify_event_t *ev = reinterpret_cast<xcb_selection_notify_event_t *>(event);
401 if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
402 return false;
403 }
404
405 // ignore?
406 return false;
407 }
408 case XCB_SELECTION_REQUEST:
409 filter_selection_request(ev_P: event);
410 return false;
411 case XCB_PROPERTY_NOTIFY: {
412 xcb_property_notify_event_t *ev = reinterpret_cast<xcb_property_notify_event_t *>(event);
413 if (ev->window == d->window && d->state == Private::WaitingForTimestamp) {
414 d->timestamp = ev->time;
415 d->gotTimestamp();
416 return true;
417 }
418 return false;
419 }
420 default:
421 return false;
422 }
423}
424
425void KSelectionOwner::timerEvent(QTimerEvent *event)
426{
427 if (!d) {
428 QObject::timerEvent(event);
429 return;
430 }
431 if (event->timerId() == d->timer.timerId()) {
432 d->timer.stop();
433 d->timeout();
434 return;
435 }
436
437 QObject::timerEvent(event);
438}
439
440#if 0
441bool KSelectionOwner::handleMessage(XEvent *)
442{
443 return false;
444}
445#endif
446
447void KSelectionOwner::filter_selection_request(void *event)
448{
449 if (!d) {
450 return;
451 }
452 xcb_selection_request_event_t *ev = reinterpret_cast<xcb_selection_request_event_t *>(event);
453
454 if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) {
455 return;
456 }
457
458 if (ev->time != XCB_CURRENT_TIME && ev->time - d->timestamp > 1U << 31) {
459 return; // too old or too new request
460 }
461
462 // qDebug() << "Got selection request";
463
464 xcb_connection_t *c = d->connection;
465 bool handled = false;
466
467 if (ev->target == Private::xa_multiple) {
468 if (ev->property != XCB_NONE) {
469 const int MAX_ATOMS = 100;
470
471 xcb_get_property_cookie_t cookie = xcb_get_property(c, delete: false, window: ev->requestor, property: ev->property, type: XCB_GET_PROPERTY_TYPE_ANY, long_offset: 0, long_length: MAX_ATOMS);
472 xcb_get_property_reply_t *reply = xcb_get_property_reply(c, cookie, e: nullptr);
473
474 if (reply && reply->format == 32 && reply->value_len % 2 == 0) {
475 xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(R: reply));
476 bool handled_array[MAX_ATOMS];
477
478 for (uint i = 0; i < reply->value_len / 2; i++) {
479 handled_array[i] = handle_selection(target_P: atoms[i * 2], property_P: atoms[i * 2 + 1], requestor_P: ev->requestor);
480 }
481
482 bool all_handled = true;
483 for (uint i = 0; i < reply->value_len / 2; i++) {
484 if (!handled_array[i]) {
485 all_handled = false;
486 atoms[i * 2 + 1] = XCB_NONE;
487 }
488 }
489
490 if (!all_handled) {
491 xcb_change_property(c,
492 mode: ev->requestor,
493 window: ev->property,
494 property: XCB_ATOM_ATOM,
495 type: 32,
496 format: XCB_PROP_MODE_REPLACE,
497 data_len: reply->value_len,
498 data: reinterpret_cast<const void *>(atoms));
499 }
500
501 handled = true;
502 }
503
504 if (reply) {
505 free(ptr: reply);
506 }
507 }
508 } else {
509 if (ev->property == XCB_NONE) { // obsolete client
510 ev->property = ev->target;
511 }
512
513 handled = handle_selection(target_P: ev->target, property_P: ev->property, requestor_P: ev->requestor);
514 }
515
516 KXcbEvent<xcb_selection_notify_event_t> xev;
517 xev.response_type = XCB_SELECTION_NOTIFY;
518 xev.selection = ev->selection;
519 xev.requestor = ev->requestor;
520 xev.target = ev->target;
521 xev.property = handled ? ev->property : XCB_NONE;
522
523 xcb_send_event(c, propagate: false, destination: ev->requestor, event_mask: 0, event: xev.buffer());
524}
525
526bool KSelectionOwner::handle_selection(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P)
527{
528 if (!d) {
529 return false;
530 }
531 if (target_P == Private::xa_timestamp) {
532 // qDebug() << "Handling timestamp request";
533 xcb_change_property(c: d->connection,
534 mode: requestor_P,
535 window: property_P,
536 property: XCB_ATOM_INTEGER,
537 type: 32,
538 format: XCB_PROP_MODE_REPLACE,
539 data_len: 1,
540 data: reinterpret_cast<const void *>(&d->timestamp));
541 } else if (target_P == Private::xa_targets) {
542 replyTargets(property: property_P, requestor: requestor_P);
543 } else if (genericReply(target: target_P, property: property_P, requestor: requestor_P)) {
544 // handled
545 } else {
546 return false; // unknown
547 }
548
549 return true;
550}
551
552void KSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P)
553{
554 if (!d) {
555 return;
556 }
557 xcb_atom_t atoms[3] = {Private::xa_multiple, Private::xa_timestamp, Private::xa_targets};
558
559 xcb_change_property(c: d->connection,
560 mode: requestor_P,
561 window: property_P,
562 property: XCB_ATOM_ATOM,
563 type: 32,
564 format: XCB_PROP_MODE_REPLACE,
565 data_len: sizeof(atoms) / sizeof(atoms[0]),
566 data: reinterpret_cast<const void *>(atoms));
567
568 // qDebug() << "Handling targets request";
569}
570
571bool KSelectionOwner::genericReply(xcb_atom_t, xcb_atom_t, xcb_window_t)
572{
573 return false;
574}
575
576void KSelectionOwner::getAtoms()
577{
578 if (!d) {
579 return;
580 }
581 if (Private::manager_atom != XCB_NONE) {
582 return;
583 }
584
585 xcb_connection_t *c = d->connection;
586
587 struct {
588 const char *name;
589 xcb_atom_t *atom;
590 } atoms[] = {{.name: "MANAGER", .atom: &Private::manager_atom},
591 {.name: "MULTIPLE", .atom: &Private::xa_multiple},
592 {.name: "TARGETS", .atom: &Private::xa_targets},
593 {.name: "TIMESTAMP", .atom: &Private::xa_timestamp}};
594
595 const int count = sizeof(atoms) / sizeof(atoms[0]);
596 xcb_intern_atom_cookie_t cookies[count];
597
598 for (int i = 0; i < count; i++) {
599 cookies[i] = xcb_intern_atom(c, only_if_exists: false, name_len: strlen(s: atoms[i].name), name: atoms[i].name);
600 }
601
602 for (int i = 0; i < count; i++) {
603 if (xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie: cookies[i], e: nullptr)) {
604 *atoms[i].atom = reply->atom;
605 free(ptr: reply);
606 }
607 }
608}
609
610xcb_atom_t KSelectionOwner::Private::manager_atom = XCB_NONE;
611xcb_atom_t KSelectionOwner::Private::xa_multiple = XCB_NONE;
612xcb_atom_t KSelectionOwner::Private::xa_targets = XCB_NONE;
613xcb_atom_t KSelectionOwner::Private::xa_timestamp = XCB_NONE;
614
615#include "moc_kselectionowner.cpp"
616

source code of kwindowsystem/src/platforms/xcb/kselectionowner.cpp