File indexing completed on 2024-05-05 12:24:28

0001 /*
0002     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "kselectionwatcher.h"
0008 
0009 #include "kwindowsystem.h"
0010 #include <config-kwindowsystem.h>
0011 
0012 #include <QAbstractNativeEventFilter>
0013 #include <QCoreApplication>
0014 
0015 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0016 #include <private/qtx11extras_p.h>
0017 #else
0018 #include <QX11Info>
0019 #endif
0020 
0021 static xcb_window_t get_selection_owner(xcb_connection_t *c, xcb_atom_t selection)
0022 {
0023     xcb_window_t owner = XCB_NONE;
0024     xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(c, xcb_get_selection_owner(c, selection), nullptr);
0025 
0026     if (reply) {
0027         owner = reply->owner;
0028         free(reply);
0029     }
0030 
0031     return owner;
0032 }
0033 
0034 static xcb_atom_t intern_atom(xcb_connection_t *c, const char *name)
0035 {
0036     xcb_atom_t atom = XCB_NONE;
0037     xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(name), name), nullptr);
0038 
0039     if (reply) {
0040         atom = reply->atom;
0041         free(reply);
0042     }
0043 
0044     return atom;
0045 }
0046 
0047 //*******************************************
0048 // KSelectionWatcher
0049 //*******************************************
0050 
0051 class Q_DECL_HIDDEN KSelectionWatcher::Private : public QAbstractNativeEventFilter
0052 {
0053 public:
0054     Private(KSelectionWatcher *watcher_P, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
0055         : connection(c)
0056         , root(root)
0057         , selection(selection_P)
0058         , selection_owner(XCB_NONE)
0059         , watcher(watcher_P)
0060     {
0061         QCoreApplication::instance()->installNativeEventFilter(this);
0062     }
0063 
0064     xcb_connection_t *connection;
0065     xcb_window_t root;
0066     const xcb_atom_t selection;
0067     xcb_window_t selection_owner;
0068     static xcb_atom_t manager_atom;
0069 
0070     static Private *create(KSelectionWatcher *watcher, xcb_atom_t selection_P, int screen_P);
0071     static Private *create(KSelectionWatcher *watcher, const char *selection_P, int screen_P);
0072     static Private *create(KSelectionWatcher *watcher, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root);
0073     static Private *create(KSelectionWatcher *watcher, const char *selection_P, xcb_connection_t *c, xcb_window_t root);
0074 
0075 protected:
0076 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0077     bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
0078 #else
0079     bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override
0080 #endif
0081     {
0082         if (eventType != "xcb_generic_event_t") {
0083             return false;
0084         }
0085         watcher->filterEvent(message);
0086         return false;
0087     }
0088 
0089 private:
0090     KSelectionWatcher *watcher;
0091 };
0092 
0093 KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, xcb_atom_t selection_P, int screen_P)
0094 {
0095     if (KWindowSystem::isPlatformX11()) {
0096         return create(watcher, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
0097     }
0098     return nullptr;
0099 }
0100 
0101 KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, xcb_atom_t selection_P, xcb_connection_t *c, xcb_window_t root)
0102 {
0103     return new Private(watcher, selection_P, c, root);
0104 }
0105 
0106 KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, const char *selection_P, int screen_P)
0107 {
0108     if (KWindowSystem::isPlatformX11()) {
0109         return create(watcher, selection_P, QX11Info::connection(), QX11Info::appRootWindow(screen_P));
0110     }
0111     return nullptr;
0112 }
0113 
0114 KSelectionWatcher::Private *KSelectionWatcher::Private::create(KSelectionWatcher *watcher, const char *selection_P, xcb_connection_t *c, xcb_window_t root)
0115 {
0116     return new Private(watcher, intern_atom(c, selection_P), c, root);
0117 }
0118 
0119 KSelectionWatcher::KSelectionWatcher(xcb_atom_t selection_P, int screen_P, QObject *parent_P)
0120     : QObject(parent_P)
0121     , d(Private::create(this, selection_P, screen_P))
0122 {
0123     init();
0124 }
0125 
0126 KSelectionWatcher::KSelectionWatcher(const char *selection_P, int screen_P, QObject *parent_P)
0127     : QObject(parent_P)
0128     , d(Private::create(this, selection_P, screen_P))
0129 {
0130     init();
0131 }
0132 
0133 KSelectionWatcher::KSelectionWatcher(xcb_atom_t selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
0134     : QObject(parent)
0135     , d(Private::create(this, selection, c, root))
0136 {
0137     init();
0138 }
0139 
0140 KSelectionWatcher::KSelectionWatcher(const char *selection, xcb_connection_t *c, xcb_window_t root, QObject *parent)
0141     : QObject(parent)
0142     , d(Private::create(this, selection, c, root))
0143 {
0144     init();
0145 }
0146 
0147 KSelectionWatcher::~KSelectionWatcher()
0148 {
0149     delete d;
0150 }
0151 
0152 void KSelectionWatcher::init()
0153 {
0154     if (!d) {
0155         return;
0156     }
0157     if (Private::manager_atom == XCB_NONE) {
0158         xcb_connection_t *c = d->connection;
0159 
0160         xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom(c, false, strlen("MANAGER"), "MANAGER");
0161         xcb_get_window_attributes_cookie_t attr_cookie = xcb_get_window_attributes(c, d->root);
0162 
0163         xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(c, atom_cookie, nullptr);
0164         Private::manager_atom = atom_reply->atom;
0165         free(atom_reply);
0166 
0167         xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(c, attr_cookie, nullptr);
0168         uint32_t event_mask = attr->your_event_mask;
0169         free(attr);
0170 
0171         if (!(event_mask & XCB_EVENT_MASK_STRUCTURE_NOTIFY)) {
0172             // We need XCB_EVENT_MASK_STRUCTURE_NORITY on the root window
0173             event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
0174             xcb_change_window_attributes(c, d->root, XCB_CW_EVENT_MASK, &event_mask);
0175         }
0176     }
0177 
0178     owner(); // trigger reading of current selection status
0179 }
0180 
0181 xcb_window_t KSelectionWatcher::owner()
0182 {
0183     if (!d) {
0184         return XCB_WINDOW_NONE;
0185     }
0186     xcb_connection_t *c = d->connection;
0187 
0188     xcb_window_t current_owner = get_selection_owner(c, d->selection);
0189     if (current_owner == XCB_NONE) {
0190         return XCB_NONE;
0191     }
0192 
0193     if (current_owner == d->selection_owner) {
0194         return d->selection_owner;
0195     }
0196 
0197     // We have a new selection owner - select for structure notify events
0198     uint32_t mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
0199     xcb_void_cookie_t cookie = xcb_change_window_attributes_checked(c, current_owner, XCB_CW_EVENT_MASK, &mask);
0200 
0201     // Verify that the owner didn't change again while selecting for events
0202     xcb_window_t new_owner = get_selection_owner(c, d->selection);
0203     xcb_generic_error_t *err = xcb_request_check(c, cookie);
0204 
0205     if (!err && current_owner == new_owner) {
0206         d->selection_owner = current_owner;
0207         Q_EMIT newOwner(d->selection_owner);
0208     } else {
0209         // ### This doesn't look right - the selection could have an owner
0210         d->selection_owner = XCB_NONE;
0211     }
0212 
0213     if (err) {
0214         free(err);
0215     }
0216 
0217     return d->selection_owner;
0218 }
0219 
0220 void KSelectionWatcher::filterEvent(void *ev_P)
0221 {
0222     if (!d) {
0223         return;
0224     }
0225     xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(ev_P);
0226     const uint response_type = event->response_type & ~0x80;
0227     if (response_type == XCB_CLIENT_MESSAGE) {
0228         xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
0229 
0230         if (cm_event->type != Private::manager_atom || cm_event->data.data32[1] != d->selection) {
0231             return;
0232         }
0233         // owner() checks whether the owner changed and emits newOwner()
0234         owner();
0235         return;
0236     }
0237     if (response_type == XCB_DESTROY_NOTIFY) {
0238         xcb_destroy_notify_event_t *ev = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
0239         if (d->selection_owner == XCB_NONE || ev->window != d->selection_owner) {
0240             return;
0241         }
0242 
0243         d->selection_owner = XCB_NONE; // in case the exactly same ID gets reused as the owner
0244 
0245         if (owner() == XCB_NONE) {
0246             Q_EMIT lostOwner(); // it must be safe to delete 'this' in a slot
0247         }
0248         return;
0249     }
0250 }
0251 
0252 xcb_atom_t KSelectionWatcher::Private::manager_atom = XCB_NONE;
0253 
0254 #include "moc_kselectionwatcher.cpp"