KWindowSystem

kselectionowner.cpp
1 /*
2  SPDX-FileCopyrightText: 2003 Lubos Lunak <[email protected]>
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 <qx11info_x11.h>
19 
20 static 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 
33 static 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 
46 class Q_DECL_HIDDEN KSelectionOwner::Private : public QAbstractNativeEventFilter
47 {
48 public:
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 
91 protected:
92  bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override
93  {
94  Q_UNUSED(result);
95  if (eventType != "xcb_generic_event_t") {
96  return false;
97  }
98  return owner->filterEvent(message);
99  }
100 
101 private:
102  KSelectionOwner *owner;
103 };
104 
105 KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P)
106 {
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 
114 KSelectionOwner::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 
119 KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, int screen_P)
120 {
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 
128 KSelectionOwner::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 
133 KSelectionOwner::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 
139 KSelectionOwner::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 
145 KSelectionOwner::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 
151 KSelectionOwner::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 
168 void KSelectionOwner::Private::claimSucceeded()
169 {
170  state = Idle;
171 
172  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, (const char *)&ev);
184 
185  // qDebug() << "Claimed selection";
186 
187  Q_EMIT owner->claimedOwnership();
188 }
189 
190 void 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 
225 void 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 
245 void 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 
320 xcb_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 
332 void 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) {
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
441 bool KSelectionOwner::handleMessage(XEvent *)
442 {
443  return false;
444 }
445 #endif
446 
447 void 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  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, (const char *)&xev);
524 }
525 
526 bool 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 
552 void 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 
571 bool 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 
610 xcb_atom_t KSelectionOwner::Private::manager_atom = XCB_NONE;
611 xcb_atom_t KSelectionOwner::Private::xa_multiple = XCB_NONE;
612 xcb_atom_t KSelectionOwner::Private::xa_targets = XCB_NONE;
613 xcb_atom_t KSelectionOwner::Private::xa_timestamp = XCB_NONE;
void timerEvent(QTimerEvent *event) override
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
This class implements claiming and owning manager selections, as described in the ICCCM...
QAction * create(StandardAction id, const QObject *recvr, Func slot, QObject *parent)
void start(int msec, QObject *object)
bool filterEvent(void *ev_P)
void release()
If the selection is owned, the ownership is given up.
virtual void timerEvent(QTimerEvent *event)
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...
void lostOwnership()
This signal is emitted if the selection was owned and the ownership has been lost due to another clie...
virtual bool event(QEvent *e)
QVector< V > values(const QMultiHash< K, V > &c)
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.
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result)=0
static bool isPlatformX11()
Convenience method to check whether the Platform is X11.
QCoreApplication * instance()
void claim(bool force, bool force_kill=true)
Try to claim ownership of the manager selection using the current X timestamp.
KSelectionOwner(xcb_atom_t selection, int screen=-1, QObject *parent=nullptr)
This constructor initializes the object, but doesn&#39;t perform any operation on the selection...
void installNativeEventFilter(QAbstractNativeEventFilter *filterObj)
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 failedToClaimOwnership()
This signal is emitted when claim() failed to claim ownership of the selection.
virtual void getAtoms()
Called to create atoms needed for claiming the selection and communication using the selection handli...
~KSelectionOwner() override
Destructor.
void claimedOwnership()
This signal is emitted when claim() was successful in claiming ownership of the selection.
int timerId() const const
xcb_window_t ownerWindow() const
If the selection is owned, returns the window used internally for owning the selection.
QObject * parent() const const
State
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Fri Oct 15 2021 22:41:49 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.