File indexing completed on 2024-05-05 16:19:50
0001 /* 0002 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include "kselectionowner.h" 0008 0009 #include "kwindowsystem.h" 0010 #include <config-kwindowsystem.h> 0011 0012 #include <QAbstractNativeEventFilter> 0013 #include <QBasicTimer> 0014 #include <QDebug> 0015 #include <QGuiApplication> 0016 #include <QTimerEvent> 0017 0018 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0019 #include <private/qtx11extras_p.h> 0020 #else 0021 #include <QX11Info> 0022 #endif 0023 0024 static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection) 0025 { 0026 xcb_window_t owner = XCB_NONE; 0027 xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, xcb_get_selection_owner(c, selection), nullptr); 0028 0029 if (reply) { 0030 owner = reply->owner; 0031 free(reply); 0032 } 0033 0034 return owner; 0035 } 0036 0037 static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name) 0038 { 0039 xcb_atom_t atom = XCB_NONE; 0040 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(name), name), nullptr); 0041 0042 if (reply) { 0043 atom = reply->atom; 0044 free(reply); 0045 } 0046 0047 return atom; 0048 } 0049 0050 class Q_DECL_HIDDEN KSelectionOwner::Private : public QAbstractNativeEventFilter 0051 { 0052 public: 0053 enum State { Idle, WaitingForTimestamp, WaitingForPreviousOwner }; 0054 0055 Private(KSelectionOwner *owner_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root) 0056 : state(Idle) 0057 , selection(selection_P) 0058 , connection(c) 0059 , root(root) 0060 , window(XCB_NONE) 0061 , prev_owner(XCB_NONE) 0062 , timestamp(XCB_CURRENT_TIME) 0063 , extra1(0) 0064 , extra2(0) 0065 , force_kill(false) 0066 , owner(owner_P) 0067 { 0068 QCoreApplication::instance()->installNativeEventFilter(this); 0069 } 0070 0071 void claimSucceeded(); 0072 void gotTimestamp(); 0073 void timeout(); 0074 0075 State state; 0076 const xcb_atom_t selection; 0077 xcb_connection_t *connection; 0078 xcb_window_t root; 0079 xcb_window_t window; 0080 xcb_window_t prev_owner; 0081 xcb_timestamp_t timestamp; 0082 uint32_t extra1, extra2; 0083 QBasicTimer timer; 0084 bool force_kill; 0085 static xcb_atom_t manager_atom; 0086 static xcb_atom_t xa_multiple; 0087 static xcb_atom_t xa_targets; 0088 static xcb_atom_t xa_timestamp; 0089 0090 static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P); 0091 static Private *create(KSelectionOwner *owner, const char *selection_P, int screen_P); 0092 static Private *create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root); 0093 static Private *create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root); 0094 0095 protected: 0096 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0097 bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override 0098 #else 0099 bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override 0100 #endif 0101 { 0102 if (eventType != "xcb_generic_event_t") { 0103 return false; 0104 } 0105 return owner->filterEvent(message); 0106 } 0107 0108 private: 0109 KSelectionOwner *owner; 0110 }; 0111 0112 KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, int screen_P) 0113 { 0114 if (KWindowSystem::isPlatformX11()) { 0115 return create(owner, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P)); 0116 } 0117 qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug."; 0118 return nullptr; 0119 } 0120 0121 KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root) 0122 { 0123 return new Private(owner, selection_P, c, root); 0124 } 0125 0126 KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, int screen_P) 0127 { 0128 if (KWindowSystem::isPlatformX11()) { 0129 return create(owner, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P)); 0130 } 0131 qWarning() << "Trying to use KSelectionOwner on a non-X11 platform! This is an application bug."; 0132 return nullptr; 0133 } 0134 0135 KSelectionOwner::Private *KSelectionOwner::Private::create(KSelectionOwner *owner, const char *selection_P, xcb_connection_t *c, xcb_window_t root) 0136 { 0137 return new Private(owner, intern_atom(c, selection_P), c, root); 0138 } 0139 0140 KSelectionOwner::KSelectionOwner(xcb_atom_t selection_P, int screen_P, QObject *parent_P) 0141 : QObject(parent_P) 0142 , d(Private::create(this, selection_P, screen_P)) 0143 { 0144 } 0145 0146 KSelectionOwner::KSelectionOwner(const char *selection_P, int screen_P, QObject *parent_P) 0147 : QObject(parent_P) 0148 , d(Private::create(this, selection_P, screen_P)) 0149 { 0150 } 0151 0152 KSelectionOwner::KSelectionOwner(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent) 0153 : QObject(parent) 0154 , d(Private::create(this, selection, c, root)) 0155 { 0156 } 0157 0158 KSelectionOwner::KSelectionOwner(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent) 0159 : QObject(parent) 0160 , d(Private::create(this, selection, c, root)) 0161 { 0162 } 0163 0164 KSelectionOwner::~KSelectionOwner() 0165 { 0166 if (d) { 0167 release(); 0168 if (d->window != XCB_WINDOW_NONE) { 0169 xcb_destroy_window(d->connection, d->window); // also makes the selection not owned 0170 } 0171 delete d; 0172 } 0173 } 0174 0175 void KSelectionOwner::Private::claimSucceeded() 0176 { 0177 state = Idle; 0178 0179 xcb_client_message_event_t ev; 0180 ev.response_type = XCB_CLIENT_MESSAGE; 0181 ev.format = 32; 0182 ev.window = root; 0183 ev.type = Private::manager_atom; 0184 ev.data.data32[0] = timestamp; 0185 ev.data.data32[1] = selection; 0186 ev.data.data32[2] = window; 0187 ev.data.data32[3] = extra1; 0188 ev.data.data32[4] = extra2; 0189 0190 xcb_send_event(connection, false, root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (const char *)&ev); 0191 0192 // qDebug() << "Claimed selection"; 0193 0194 Q_EMIT owner->claimedOwnership(); 0195 } 0196 0197 void KSelectionOwner::Private::gotTimestamp() 0198 { 0199 Q_ASSERT(state == WaitingForTimestamp); 0200 0201 state = Idle; 0202 0203 xcb_connection_t *c = connection; 0204 0205 // Set the selection owner and immediately verify that the claim was successful 0206 xcb_set_selection_owner(c, window, selection, timestamp); 0207 xcb_window_t new_owner = get_selection_owner(c, selection); 0208 0209 if (new_owner != window) { 0210 // qDebug() << "Failed to claim selection : " << new_owner; 0211 xcb_destroy_window(c, window); 0212 timestamp = XCB_CURRENT_TIME; 0213 window = XCB_NONE; 0214 0215 Q_EMIT owner->failedToClaimOwnership(); 0216 return; 0217 } 0218 0219 if (prev_owner != XCB_NONE && force_kill) { 0220 // qDebug() << "Waiting for previous owner to disown"; 0221 timer.start(1000, owner); 0222 state = WaitingForPreviousOwner; 0223 0224 // Note: We've already selected for structure notify events 0225 // on the previous owner window 0226 } else { 0227 // If there was no previous owner, we're done 0228 claimSucceeded(); 0229 } 0230 } 0231 0232 void KSelectionOwner::Private::timeout() 0233 { 0234 Q_ASSERT(state == WaitingForPreviousOwner); 0235 0236 state = Idle; 0237 0238 if (force_kill) { 0239 // qDebug() << "Killing previous owner"; 0240 xcb_connection_t *c = connection; 0241 0242 // Ignore any errors from the kill request 0243 xcb_generic_error_t *err = xcb_request_check(c, xcb_kill_client_checked(c, prev_owner)); 0244 free(err); 0245 0246 claimSucceeded(); 0247 } else { 0248 Q_EMIT owner->failedToClaimOwnership(); 0249 } 0250 } 0251 0252 void KSelectionOwner::claim(bool force_P, bool force_kill_P) 0253 { 0254 if (!d) { 0255 return; 0256 } 0257 Q_ASSERT(d->state == Private::Idle); 0258 0259 if (Private::manager_atom == XCB_NONE) { 0260 getAtoms(); 0261 } 0262 0263 if (d->timestamp != XCB_CURRENT_TIME) { 0264 release(); 0265 } 0266 0267 xcb_connection_t *c = d->connection; 0268 d->prev_owner = get_selection_owner(c, d->selection); 0269 0270 if (d->prev_owner != XCB_NONE) { 0271 if (!force_P) { 0272 // qDebug() << "Selection already owned, failing"; 0273 Q_EMIT failedToClaimOwnership(); 0274 return; 0275 } 0276 0277 // Select structure notify events so get an event when the previous owner 0278 // destroys the window 0279 uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY; 0280 xcb_change_window_attributes(c, d->prev_owner, XCB_CW_EVENT_MASK, &mask); 0281 } 0282 0283 uint32_t values[] = {true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY}; 0284 0285 d->window = xcb_generate_id(c); 0286 xcb_create_window(c, 0287 XCB_COPY_FROM_PARENT, 0288 d->window, 0289 d->root, 0290 0, 0291 0, 0292 1, 0293 1, 0294 0, 0295 XCB_WINDOW_CLASS_INPUT_ONLY, 0296 XCB_COPY_FROM_PARENT, 0297 XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, 0298 values); 0299 0300 // Trigger a property change event so we get a timestamp 0301 xcb_atom_t tmp = XCB_ATOM_ATOM; 0302 xcb_change_property(c, XCB_PROP_MODE_REPLACE, d->window, XCB_ATOM_ATOM, XCB_ATOM_ATOM, 32, 1, (const void *)&tmp); 0303 0304 // Now we have to return to the event loop and wait for the property change event 0305 d->force_kill = force_kill_P; 0306 d->state = Private::WaitingForTimestamp; 0307 } 0308 0309 // destroy resource first 0310 void KSelectionOwner::release() 0311 { 0312 if (!d) { 0313 return; 0314 } 0315 if (d->timestamp == XCB_CURRENT_TIME) { 0316 return; 0317 } 0318 0319 xcb_destroy_window(d->connection, d->window); // also makes the selection not owned 0320 d->window = XCB_NONE; 0321 0322 // qDebug() << "Releasing selection"; 0323 0324 d->timestamp = XCB_CURRENT_TIME; 0325 } 0326 0327 xcb_window_t KSelectionOwner::ownerWindow() const 0328 { 0329 if (!d) { 0330 return XCB_WINDOW_NONE; 0331 } 0332 if (d->timestamp == XCB_CURRENT_TIME) { 0333 return XCB_NONE; 0334 } 0335 0336 return d->window; 0337 } 0338 0339 void KSelectionOwner::setData(uint32_t extra1_P, uint32_t extra2_P) 0340 { 0341 if (!d) { 0342 return; 0343 } 0344 d->extra1 = extra1_P; 0345 d->extra2 = extra2_P; 0346 } 0347 0348 bool KSelectionOwner::filterEvent(void *ev_P) 0349 { 0350 if (!d) { 0351 return false; 0352 } 0353 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P); 0354 const uint response_type = event->response_type & ~0x80; 0355 0356 #if 0 0357 // There's no generic way to get the window for an event in xcb, it depends on the type of event 0358 // This handleMessage virtual doesn't seem used anyway. 0359 if (d->timestamp != CurrentTime && ev_P->xany.window == d->window) { 0360 if (handleMessage(ev_P)) { 0361 return true; 0362 } 0363 } 0364 #endif 0365 switch (response_type) { 0366 case XCB_SELECTION_CLEAR: { 0367 xcb_selection_clear_event_t *ev = reinterpret_cast<xcb_selection_clear_event_t *>(event); 0368 if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) { 0369 return false; 0370 } 0371 0372 d->timestamp = XCB_CURRENT_TIME; 0373 // qDebug() << "Lost selection"; 0374 0375 xcb_window_t window = d->window; 0376 Q_EMIT lostOwnership(); 0377 0378 // Unset the event mask before we destroy the window so we don't get a destroy event 0379 uint32_t event_mask = XCB_NONE; 0380 xcb_change_window_attributes(d->connection, window, XCB_CW_EVENT_MASK, &event_mask); 0381 xcb_destroy_window(d->connection, window); 0382 return true; 0383 } 0384 case XCB_DESTROY_NOTIFY: { 0385 xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event); 0386 if (ev->window == d->prev_owner) { 0387 if (d->state == Private::WaitingForPreviousOwner) { 0388 d->timer.stop(); 0389 d->claimSucceeded(); 0390 return true; 0391 } 0392 // It is possible for the previous owner to be destroyed 0393 // while we're waiting for the timestamp 0394 d->prev_owner = XCB_NONE; 0395 } 0396 0397 if (d->timestamp == XCB_CURRENT_TIME || ev->window != d->window) { 0398 return false; 0399 } 0400 0401 d->timestamp = XCB_CURRENT_TIME; 0402 // qDebug() << "Lost selection (destroyed)"; 0403 Q_EMIT lostOwnership(); 0404 return true; 0405 } 0406 case XCB_SELECTION_NOTIFY: { 0407 xcb_selection_notify_event_t *ev = reinterpret_cast<xcb_selection_notify_event_t *>(event); 0408 if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) { 0409 return false; 0410 } 0411 0412 // ignore? 0413 return false; 0414 } 0415 case XCB_SELECTION_REQUEST: 0416 filter_selection_request(event); 0417 return false; 0418 case XCB_PROPERTY_NOTIFY: { 0419 xcb_property_notify_event_t *ev = reinterpret_cast<xcb_property_notify_event_t *>(event); 0420 if (ev->window == d->window && d->state == Private::WaitingForTimestamp) { 0421 d->timestamp = ev->time; 0422 d->gotTimestamp(); 0423 return true; 0424 } 0425 return false; 0426 } 0427 default: 0428 return false; 0429 } 0430 } 0431 0432 void KSelectionOwner::timerEvent(QTimerEvent *event) 0433 { 0434 if (!d) { 0435 QObject::timerEvent(event); 0436 return; 0437 } 0438 if (event->timerId() == d->timer.timerId()) { 0439 d->timer.stop(); 0440 d->timeout(); 0441 return; 0442 } 0443 0444 QObject::timerEvent(event); 0445 } 0446 0447 #if 0 0448 bool KSelectionOwner::handleMessage(XEvent *) 0449 { 0450 return false; 0451 } 0452 #endif 0453 0454 void KSelectionOwner::filter_selection_request(void *event) 0455 { 0456 if (!d) { 0457 return; 0458 } 0459 xcb_selection_request_event_t *ev = reinterpret_cast<xcb_selection_request_event_t *>(event); 0460 0461 if (d->timestamp == XCB_CURRENT_TIME || ev->selection != d->selection) { 0462 return; 0463 } 0464 0465 if (ev->time != XCB_CURRENT_TIME && ev->time - d->timestamp > 1U << 31) { 0466 return; // too old or too new request 0467 } 0468 0469 // qDebug() << "Got selection request"; 0470 0471 xcb_connection_t *c = d->connection; 0472 bool handled = false; 0473 0474 if (ev->target == Private::xa_multiple) { 0475 if (ev->property != XCB_NONE) { 0476 const int MAX_ATOMS = 100; 0477 0478 xcb_get_property_cookie_t cookie = xcb_get_property(c, false, ev->requestor, ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, MAX_ATOMS); 0479 xcb_get_property_reply_t *reply = xcb_get_property_reply(c, cookie, nullptr); 0480 0481 if (reply && reply->format == 32 && reply->value_len % 2 == 0) { 0482 xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(reply)); 0483 bool handled_array[MAX_ATOMS]; 0484 0485 for (uint i = 0; i < reply->value_len / 2; i++) { 0486 handled_array[i] = handle_selection(atoms[i * 2], atoms[i * 2 + 1], ev->requestor); 0487 } 0488 0489 bool all_handled = true; 0490 for (uint i = 0; i < reply->value_len / 2; i++) { 0491 if (!handled_array[i]) { 0492 all_handled = false; 0493 atoms[i * 2 + 1] = XCB_NONE; 0494 } 0495 } 0496 0497 if (!all_handled) { 0498 xcb_change_property(c, 0499 ev->requestor, 0500 ev->property, 0501 XCB_ATOM_ATOM, 0502 32, 0503 XCB_PROP_MODE_REPLACE, 0504 reply->value_len, 0505 reinterpret_cast<const void *>(atoms)); 0506 } 0507 0508 handled = true; 0509 } 0510 0511 if (reply) { 0512 free(reply); 0513 } 0514 } 0515 } else { 0516 if (ev->property == XCB_NONE) { // obsolete client 0517 ev->property = ev->target; 0518 } 0519 0520 handled = handle_selection(ev->target, ev->property, ev->requestor); 0521 } 0522 0523 xcb_selection_notify_event_t xev; 0524 xev.response_type = XCB_SELECTION_NOTIFY; 0525 xev.selection = ev->selection; 0526 xev.requestor = ev->requestor; 0527 xev.target = ev->target; 0528 xev.property = handled ? ev->property : XCB_NONE; 0529 0530 xcb_send_event(c, false, ev->requestor, 0, (const char *)&xev); 0531 } 0532 0533 bool KSelectionOwner::handle_selection(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P) 0534 { 0535 if (!d) { 0536 return false; 0537 } 0538 if (target_P == Private::xa_timestamp) { 0539 // qDebug() << "Handling timestamp request"; 0540 xcb_change_property(d->connection, 0541 requestor_P, 0542 property_P, 0543 XCB_ATOM_INTEGER, 0544 32, 0545 XCB_PROP_MODE_REPLACE, 0546 1, 0547 reinterpret_cast<const void *>(&d->timestamp)); 0548 } else if (target_P == Private::xa_targets) { 0549 replyTargets(property_P, requestor_P); 0550 } else if (genericReply(target_P, property_P, requestor_P)) { 0551 // handled 0552 } else { 0553 return false; // unknown 0554 } 0555 0556 return true; 0557 } 0558 0559 void KSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P) 0560 { 0561 if (!d) { 0562 return; 0563 } 0564 xcb_atom_t atoms[3] = {Private::xa_multiple, Private::xa_timestamp, Private::xa_targets}; 0565 0566 xcb_change_property(d->connection, 0567 requestor_P, 0568 property_P, 0569 XCB_ATOM_ATOM, 0570 32, 0571 XCB_PROP_MODE_REPLACE, 0572 sizeof(atoms) / sizeof(atoms[0]), 0573 reinterpret_cast<const void *>(atoms)); 0574 0575 // qDebug() << "Handling targets request"; 0576 } 0577 0578 bool KSelectionOwner::genericReply(xcb_atom_t, xcb_atom_t, xcb_window_t) 0579 { 0580 return false; 0581 } 0582 0583 void KSelectionOwner::getAtoms() 0584 { 0585 if (!d) { 0586 return; 0587 } 0588 if (Private::manager_atom != XCB_NONE) { 0589 return; 0590 } 0591 0592 xcb_connection_t *c = d->connection; 0593 0594 struct { 0595 const char *name; 0596 xcb_atom_t *atom; 0597 } atoms[] = {{"MANAGER", &Private::manager_atom}, 0598 {"MULTIPLE", &Private::xa_multiple}, 0599 {"TARGETS", &Private::xa_targets}, 0600 {"TIMESTAMP", &Private::xa_timestamp}}; 0601 0602 const int count = sizeof(atoms) / sizeof(atoms[0]); 0603 xcb_intern_atom_cookie_t cookies[count]; 0604 0605 for (int i = 0; i < count; i++) { 0606 cookies[i] = xcb_intern_atom(c, false, strlen(atoms[i].name), atoms[i].name); 0607 } 0608 0609 for (int i = 0; i < count; i++) { 0610 if (xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookies[i], nullptr)) { 0611 *atoms[i].atom = reply->atom; 0612 free(reply); 0613 } 0614 } 0615 } 0616 0617 xcb_atom_t KSelectionOwner::Private::manager_atom = XCB_NONE; 0618 xcb_atom_t KSelectionOwner::Private::xa_multiple = XCB_NONE; 0619 xcb_atom_t KSelectionOwner::Private::xa_targets = XCB_NONE; 0620 xcb_atom_t KSelectionOwner::Private::xa_timestamp = XCB_NONE; 0621 0622 #include "moc_kselectionowner.cpp"