KWindowSystem

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

KDE's Doxygen guidelines are available online.