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"