File indexing completed on 2024-04-28 03:59:24

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