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

0001 /*
0002     SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org>
0003     SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: MIT
0006 */
0007 
0008 #include "kxmessages.h"
0009 #include "cptr_p.h"
0010 #include "kxutils_p.h"
0011 
0012 #if KWINDOWSYSTEM_HAVE_X11
0013 
0014 #include <QAbstractNativeEventFilter>
0015 #include <QCoreApplication>
0016 #include <QDebug>
0017 #include <QWindow> // WId
0018 
0019 #include <X11/Xlib.h>
0020 
0021 #include <private/qtx11extras_p.h>
0022 
0023 class XcbAtom
0024 {
0025 public:
0026     explicit XcbAtom(const QByteArray &name, bool onlyIfExists = false)
0027         : m_name(name)
0028         , m_atom(XCB_ATOM_NONE)
0029         , m_connection(nullptr)
0030         , m_retrieved(false)
0031         , m_onlyIfExists(onlyIfExists)
0032     {
0033         m_cookie.sequence = 0;
0034     }
0035     explicit XcbAtom(xcb_connection_t *c, const QByteArray &name, bool onlyIfExists = false)
0036         : m_name(name)
0037         , m_atom(XCB_ATOM_NONE)
0038         , m_cookie(xcb_intern_atom_unchecked(c, onlyIfExists, name.length(), name.constData()))
0039         , m_connection(c)
0040         , m_retrieved(false)
0041         , m_onlyIfExists(onlyIfExists)
0042     {
0043     }
0044 
0045     ~XcbAtom()
0046     {
0047         if (!m_retrieved && m_cookie.sequence && m_connection) {
0048             xcb_discard_reply(m_connection, m_cookie.sequence);
0049         }
0050     }
0051 
0052     operator xcb_atom_t()
0053     {
0054         getReply();
0055         return m_atom;
0056     }
0057 
0058     inline const QByteArray &name() const
0059     {
0060         return m_name;
0061     }
0062 
0063     inline void setConnection(xcb_connection_t *c)
0064     {
0065         m_connection = c;
0066     }
0067 
0068     inline void fetch()
0069     {
0070         if (!m_connection || m_name.isEmpty()) {
0071             return;
0072         }
0073         m_cookie = xcb_intern_atom_unchecked(m_connection, m_onlyIfExists, m_name.length(), m_name.constData());
0074     }
0075 
0076 private:
0077     void getReply()
0078     {
0079         if (m_retrieved || !m_cookie.sequence || !m_connection) {
0080             return;
0081         }
0082         UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(m_connection, m_cookie, nullptr));
0083         if (reply) {
0084             m_atom = reply->atom;
0085         }
0086         m_retrieved = true;
0087     }
0088     QByteArray m_name;
0089     xcb_atom_t m_atom;
0090     xcb_intern_atom_cookie_t m_cookie;
0091     xcb_connection_t *m_connection;
0092     bool m_retrieved;
0093     bool m_onlyIfExists;
0094 };
0095 
0096 class KXMessagesPrivate : public QAbstractNativeEventFilter
0097 {
0098 public:
0099     KXMessagesPrivate(KXMessages *parent, const char *acceptBroadcast, xcb_connection_t *c, xcb_window_t root)
0100         : accept_atom1(acceptBroadcast ? QByteArray(acceptBroadcast) + QByteArrayLiteral("_BEGIN") : QByteArray())
0101         , accept_atom2(acceptBroadcast ? QByteArray(acceptBroadcast) : QByteArray())
0102         , handle(new QWindow)
0103         , q(parent)
0104         , valid(c)
0105         , connection(c)
0106         , rootWindow(root)
0107     {
0108         if (acceptBroadcast) {
0109             accept_atom1.setConnection(c);
0110             accept_atom1.fetch();
0111             accept_atom2.setConnection(c);
0112             accept_atom2.fetch();
0113             QCoreApplication::instance()->installNativeEventFilter(this);
0114         }
0115     }
0116     XcbAtom accept_atom1;
0117     XcbAtom accept_atom2;
0118     QMap<WId, QByteArray> incoming_messages;
0119     std::unique_ptr<QWindow> handle;
0120     KXMessages *q;
0121     bool valid;
0122     xcb_connection_t *connection;
0123     xcb_window_t rootWindow;
0124 
0125     bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
0126     {
0127         // A faster comparison than eventType != "xcb_generic_event_t"
0128         if (eventType[0] != 'x') {
0129             return false;
0130         }
0131         xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
0132         uint response_type = event->response_type & ~0x80;
0133         if (response_type != XCB_CLIENT_MESSAGE) {
0134             return false;
0135         }
0136         xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
0137         if (cm_event->format != 8) {
0138             return false;
0139         }
0140         if (cm_event->type != accept_atom1 && cm_event->type != accept_atom2) {
0141             return false;
0142         }
0143         char buf[21]; // can't be longer
0144         // Copy the data in order to null-terminate it
0145         qstrncpy(buf, reinterpret_cast<char *>(cm_event->data.data8), 21);
0146         // qDebug() << cm_event->window << "buf=\"" << buf << "\" atom=" << (cm_event->type == accept_atom1 ? "atom1" : "atom2");
0147         if (incoming_messages.contains(cm_event->window)) {
0148             if (cm_event->type == accept_atom1)
0149             // two different messages on the same window at the same time shouldn't happen anyway
0150             {
0151                 incoming_messages[cm_event->window] = QByteArray();
0152             }
0153             incoming_messages[cm_event->window] += buf;
0154         } else {
0155             if (cm_event->type == accept_atom2) {
0156                 return false; // middle of message, but we don't have the beginning
0157             }
0158             incoming_messages[cm_event->window] = buf;
0159         }
0160         if (strlen(buf) < 20) { // last message fragment
0161             Q_EMIT q->gotMessage(QString::fromUtf8(incoming_messages[cm_event->window].constData()));
0162             incoming_messages.remove(cm_event->window);
0163         }
0164         return false; // lets other KXMessages instances get the event too
0165     }
0166 };
0167 
0168 static void
0169 send_message_internal(xcb_window_t w, const QString &msg, xcb_connection_t *c, xcb_atom_t leadingMessage, xcb_atom_t followingMessage, xcb_window_t handle);
0170 
0171 KXMessages::KXMessages(const char *accept_broadcast_P, QObject *parent_P)
0172     : QObject(parent_P)
0173     , d(new KXMessagesPrivate(this,
0174                               accept_broadcast_P,
0175                               QX11Info::isPlatformX11() ? QX11Info::connection() : nullptr,
0176                               QX11Info::isPlatformX11() ? QX11Info::appRootWindow() : 0))
0177 {
0178 }
0179 
0180 KXMessages::KXMessages(xcb_connection_t *connection, xcb_window_t rootWindow, const char *accept_broadcast, QObject *parent)
0181     : QObject(parent)
0182     , d(new KXMessagesPrivate(this, accept_broadcast, connection, rootWindow))
0183 {
0184 }
0185 
0186 KXMessages::~KXMessages()
0187 {
0188     delete d;
0189 }
0190 
0191 static xcb_screen_t *defaultScreen(xcb_connection_t *c, int screen)
0192 {
0193     for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(c)); it.rem; --screen, xcb_screen_next(&it)) {
0194         if (screen == 0) {
0195             return it.data;
0196         }
0197     }
0198     return nullptr;
0199 }
0200 
0201 void KXMessages::broadcastMessage(const char *msg_type_P, const QString &message_P, int screen_P)
0202 {
0203     if (!d->valid) {
0204         qWarning() << "KXMessages used on non-X11 platform! This is an application bug.";
0205         return;
0206     }
0207     const QByteArray msg(msg_type_P);
0208     XcbAtom a2(d->connection, msg);
0209     XcbAtom a1(d->connection, msg + QByteArrayLiteral("_BEGIN"));
0210     xcb_window_t root = screen_P == -1 ? d->rootWindow : defaultScreen(d->connection, screen_P)->root;
0211     send_message_internal(root, message_P, d->connection, a1, a2, d->handle->winId());
0212 }
0213 
0214 bool KXMessages::broadcastMessageX(xcb_connection_t *c, const char *msg_type_P, const QString &message, int screenNumber)
0215 {
0216     if (!c) {
0217         return false;
0218     }
0219     const QByteArray msg(msg_type_P);
0220     XcbAtom a2(c, msg);
0221     XcbAtom a1(c, msg + QByteArrayLiteral("_BEGIN"));
0222     const xcb_screen_t *screen = defaultScreen(c, screenNumber);
0223     if (!screen) {
0224         return false;
0225     }
0226     const xcb_window_t root = screen->root;
0227     const xcb_window_t win = xcb_generate_id(c);
0228     xcb_create_window(c, XCB_COPY_FROM_PARENT, win, root, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, 0, nullptr);
0229     send_message_internal(root, message, c, a1, a2, win);
0230     xcb_destroy_window(c, win);
0231     return true;
0232 }
0233 
0234 static void
0235 send_message_internal(xcb_window_t w, const QString &msg_P, xcb_connection_t *c, xcb_atom_t leadingMessage, xcb_atom_t followingMessage, xcb_window_t handle)
0236 {
0237     unsigned int pos = 0;
0238     QByteArray msg = msg_P.toUtf8();
0239     const size_t len = msg.size();
0240 
0241     xcb_client_message_event_t event;
0242     event.response_type = XCB_CLIENT_MESSAGE;
0243     event.format = 8;
0244     event.sequence = 0;
0245     event.window = handle;
0246     event.type = leadingMessage;
0247 
0248     do {
0249         unsigned int i;
0250         for (i = 0; i < 20 && i + pos < len; ++i) {
0251             event.data.data8[i] = msg[i + pos];
0252         }
0253         for (; i < 20; ++i) {
0254             event.data.data8[i] = 0;
0255         }
0256         xcb_send_event(c, false, w, XCB_EVENT_MASK_PROPERTY_CHANGE, (const char *)&event);
0257         event.type = followingMessage;
0258         pos += i;
0259     } while (pos <= len);
0260 
0261     xcb_flush(c);
0262 }
0263 
0264 #endif
0265 
0266 #include "moc_kxmessages.cpp"