File indexing completed on 2024-04-21 03:59:21

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