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 "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, xcb_get_selection_owner(c, selection), nullptr);
25
26 if (reply) {
27 owner = reply->owner;
28 free(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, xcb_intern_atom(c, false, strlen(name), name), nullptr);
38
39 if (reply) {
40 atom = reply->atom;
41 free(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 {
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(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, QX11Info::connection(), QX11Info::appRootWindow(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, QX11Info::connection(), QX11Info::appRootWindow(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, 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(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(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(this, 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(this, selection, c, root))
154{
155}
156
158{
159 if (d) {
160 release();
161 if (d->window != XCB_WINDOW_NONE) {
162 xcb_destroy_window(d->connection, 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(connection, false, root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, 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, window, selection, 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
209 return;
210 }
211
212 if (prev_owner != XCB_NONE && force_kill) {
213 // qDebug() << "Waiting for previous owner to disown";
214 timer.start(1000, 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, xcb_kill_client_checked(c, prev_owner));
237 free(err);
238
239 claimSucceeded();
240 } else {
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, d->selection);
262
263 if (d->prev_owner != XCB_NONE) {
264 if (!force_P) {
265 // qDebug() << "Selection already owned, failing";
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, d->prev_owner, XCB_CW_EVENT_MASK, &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 d->window,
282 d->root,
283 0,
284 0,
285 1,
286 1,
287 0,
288 XCB_WINDOW_CLASS_INPUT_ONLY,
289 XCB_COPY_FROM_PARENT,
290 XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
291 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, XCB_PROP_MODE_REPLACE, d->window, XCB_ATOM_ATOM, XCB_ATOM_ATOM, 32, 1, (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
304{
305 if (!d) {
306 return;
307 }
308 if (d->timestamp == XCB_CURRENT_TIME) {
309 return;
310 }
311
312 xcb_destroy_window(d->connection, 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
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
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;
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(d->connection, window, XCB_CW_EVENT_MASK, &event_mask);
374 xcb_destroy_window(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)";
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(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
426{
427 if (!d) {
429 return;
430 }
431 if (event->timerId() == d->timer.timerId()) {
432 d->timer.stop();
433 d->timeout();
434 return;
435 }
436
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, false, ev->requestor, ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, MAX_ATOMS);
472 xcb_get_property_reply_t *reply = xcb_get_property_reply(c, cookie, 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(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(atoms[i * 2], atoms[i * 2 + 1], 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 ev->requestor,
493 ev->property,
494 XCB_ATOM_ATOM,
495 32,
496 XCB_PROP_MODE_REPLACE,
497 reply->value_len,
498 reinterpret_cast<const void *>(atoms));
499 }
500
501 handled = true;
502 }
503
504 if (reply) {
505 free(reply);
506 }
507 }
508 } else {
509 if (ev->property == XCB_NONE) { // obsolete client
510 ev->property = ev->target;
511 }
512
513 handled = handle_selection(ev->target, ev->property, 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, false, ev->requestor, 0, 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(d->connection,
534 requestor_P,
535 property_P,
536 XCB_ATOM_INTEGER,
537 32,
538 XCB_PROP_MODE_REPLACE,
539 1,
540 reinterpret_cast<const void *>(&d->timestamp));
541 } else if (target_P == Private::xa_targets) {
542 replyTargets(property_P, requestor_P);
543 } else if (genericReply(target_P, property_P, 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(d->connection,
560 requestor_P,
561 property_P,
562 XCB_ATOM_ATOM,
563 32,
564 XCB_PROP_MODE_REPLACE,
565 sizeof(atoms) / sizeof(atoms[0]),
566 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
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[] = {{"MANAGER", &Private::manager_atom},
591 {"MULTIPLE", &Private::xa_multiple},
592 {"TARGETS", &Private::xa_targets},
593 {"TIMESTAMP", &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, false, strlen(atoms[i].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, cookies[i], nullptr)) {
604 *atoms[i].atom = reply->atom;
605 free(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"
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.
KGUIADDONS_EXPORT QWindow * window(QObject *job)
QAction * create(StandardAction id, const Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
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 Sat Dec 21 2024 17:06:21 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.