File indexing completed on 2024-05-05 16:19:53

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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0022 #include <private/qtx11extras_p.h>
0023 #else
0024 #include <QX11Info>
0025 #endif
0026 
0027 class XcbAtom
0028 {
0029 public:
0030     explicit XcbAtom(const QByteArray &name, bool onlyIfExists = false)
0031         : m_name(name)
0032         , m_atom(XCB_ATOM_NONE)
0033         , m_connection(nullptr)
0034         , m_retrieved(false)
0035         , m_onlyIfExists(onlyIfExists)
0036     {
0037         m_cookie.sequence = 0;
0038     }
0039     explicit XcbAtom(xcb_connection_t *c, const QByteArray &name, bool onlyIfExists = false)
0040         : m_name(name)
0041         , m_atom(XCB_ATOM_NONE)
0042         , m_cookie(xcb_intern_atom_unchecked(c, onlyIfExists, name.length(), name.constData()))
0043         , m_connection(c)
0044         , m_retrieved(false)
0045         , m_onlyIfExists(onlyIfExists)
0046     {
0047     }
0048 
0049     ~XcbAtom()
0050     {
0051         if (!m_retrieved && m_cookie.sequence && m_connection) {
0052             xcb_discard_reply(m_connection, m_cookie.sequence);
0053         }
0054     }
0055 
0056     operator xcb_atom_t()
0057     {
0058         getReply();
0059         return m_atom;
0060     }
0061 
0062     inline const QByteArray &name() const
0063     {
0064         return m_name;
0065     }
0066 
0067     inline void setConnection(xcb_connection_t *c)
0068     {
0069         m_connection = c;
0070     }
0071 
0072     inline void fetch()
0073     {
0074         if (!m_connection || m_name.isEmpty()) {
0075             return;
0076         }
0077         m_cookie = xcb_intern_atom_unchecked(m_connection, m_onlyIfExists, m_name.length(), m_name.constData());
0078     }
0079 
0080 private:
0081     void getReply()
0082     {
0083         if (m_retrieved || !m_cookie.sequence || !m_connection) {
0084             return;
0085         }
0086         UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(m_connection, m_cookie, nullptr));
0087         if (reply) {
0088             m_atom = reply->atom;
0089         }
0090         m_retrieved = true;
0091     }
0092     QByteArray m_name;
0093     xcb_atom_t m_atom;
0094     xcb_intern_atom_cookie_t m_cookie;
0095     xcb_connection_t *m_connection;
0096     bool m_retrieved;
0097     bool m_onlyIfExists;
0098 };
0099 
0100 class KXMessagesPrivate : public QAbstractNativeEventFilter
0101 {
0102 public:
0103     KXMessagesPrivate(KXMessages *parent, const char *acceptBroadcast, xcb_connection_t *c, xcb_window_t root)
0104         : accept_atom1(acceptBroadcast ? QByteArray(acceptBroadcast) + QByteArrayLiteral("_BEGIN") : QByteArray())
0105         , accept_atom2(acceptBroadcast ? QByteArray(acceptBroadcast) : QByteArray())
0106         , handle(new QWindow)
0107         , q(parent)
0108         , valid(c)
0109         , connection(c)
0110         , rootWindow(root)
0111     {
0112         if (acceptBroadcast) {
0113             accept_atom1.setConnection(c);
0114             accept_atom1.fetch();
0115             accept_atom2.setConnection(c);
0116             accept_atom2.fetch();
0117             QCoreApplication::instance()->installNativeEventFilter(this);
0118         }
0119     }
0120     XcbAtom accept_atom1;
0121     XcbAtom accept_atom2;
0122     QMap<WId, QByteArray> incoming_messages;
0123     std::unique_ptr<QWindow> handle;
0124     KXMessages *q;
0125     bool valid;
0126     xcb_connection_t *connection;
0127     xcb_window_t rootWindow;
0128 
0129 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0130     bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
0131 #else
0132     bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override
0133 #endif
0134     {
0135         // A faster comparison than eventType != "xcb_generic_event_t"
0136         if (eventType[0] != 'x') {
0137             return false;
0138         }
0139         xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
0140         uint response_type = event->response_type & ~0x80;
0141         if (response_type != XCB_CLIENT_MESSAGE) {
0142             return false;
0143         }
0144         xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
0145         if (cm_event->format != 8) {
0146             return false;
0147         }
0148         if (cm_event->type != accept_atom1 && cm_event->type != accept_atom2) {
0149             return false;
0150         }
0151         char buf[21]; // can't be longer
0152         // Copy the data in order to null-terminate it
0153         qstrncpy(buf, reinterpret_cast<char *>(cm_event->data.data8), 21);
0154         // qDebug() << cm_event->window << "buf=\"" << buf << "\" atom=" << (cm_event->type == accept_atom1 ? "atom1" : "atom2");
0155         if (incoming_messages.contains(cm_event->window)) {
0156             if (cm_event->type == accept_atom1)
0157             // two different messages on the same window at the same time shouldn't happen anyway
0158             {
0159                 incoming_messages[cm_event->window] = QByteArray();
0160             }
0161             incoming_messages[cm_event->window] += buf;
0162         } else {
0163             if (cm_event->type == accept_atom2) {
0164                 return false; // middle of message, but we don't have the beginning
0165             }
0166             incoming_messages[cm_event->window] = buf;
0167         }
0168         if (strlen(buf) < 20) { // last message fragment
0169             Q_EMIT q->gotMessage(QString::fromUtf8(incoming_messages[cm_event->window].constData()));
0170             incoming_messages.remove(cm_event->window);
0171         }
0172         return false; // lets other KXMessages instances get the event too
0173     }
0174 };
0175 
0176 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
0177 static void send_message_internal(WId w_P, const QString &msg_P, long mask_P, Display *disp, Atom atom1_P, Atom atom2_P, Window handle_P);
0178 // for broadcasting
0179 static const long BROADCAST_MASK = PropertyChangeMask;
0180 // CHECKME
0181 #endif
0182 static void
0183 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);
0184 
0185 KXMessages::KXMessages(const char *accept_broadcast_P, QObject *parent_P)
0186     : QObject(parent_P)
0187     , d(new KXMessagesPrivate(this,
0188                               accept_broadcast_P,
0189                               QX11Info::isPlatformX11() ? QX11Info::connection() : nullptr,
0190                               QX11Info::isPlatformX11() ? QX11Info::appRootWindow() : 0))
0191 {
0192 }
0193 
0194 KXMessages::KXMessages(xcb_connection_t *connection, xcb_window_t rootWindow, const char *accept_broadcast, QObject *parent)
0195     : QObject(parent)
0196     , d(new KXMessagesPrivate(this, accept_broadcast, connection, rootWindow))
0197 {
0198 }
0199 
0200 KXMessages::~KXMessages()
0201 {
0202     delete d;
0203 }
0204 
0205 static xcb_screen_t *defaultScreen(xcb_connection_t *c, int screen)
0206 {
0207     for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(c)); it.rem; --screen, xcb_screen_next(&it)) {
0208         if (screen == 0) {
0209             return it.data;
0210         }
0211     }
0212     return nullptr;
0213 }
0214 
0215 void KXMessages::broadcastMessage(const char *msg_type_P, const QString &message_P, int screen_P)
0216 {
0217     if (!d->valid) {
0218         qWarning() << "KXMessages used on non-X11 platform! This is an application bug.";
0219         return;
0220     }
0221     const QByteArray msg(msg_type_P);
0222     XcbAtom a2(d->connection, msg);
0223     XcbAtom a1(d->connection, msg + QByteArrayLiteral("_BEGIN"));
0224     xcb_window_t root = screen_P == -1 ? d->rootWindow : defaultScreen(d->connection, screen_P)->root;
0225     send_message_internal(root, message_P, d->connection, a1, a2, d->handle->winId());
0226 }
0227 
0228 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
0229 bool KXMessages::broadcastMessageX(Display *disp, const char *msg_type_P, const QString &message_P, int screen_P)
0230 {
0231     if (disp == nullptr) {
0232         return false;
0233     }
0234     Atom a2 = XInternAtom(disp, msg_type_P, false);
0235     Atom a1 = XInternAtom(disp, QByteArray(QByteArray(msg_type_P) + "_BEGIN").constData(), false);
0236     Window root = screen_P == -1 ? DefaultRootWindow(disp) : RootWindow(disp, screen_P);
0237     Window win = XCreateSimpleWindow(disp,
0238                                      root,
0239                                      0,
0240                                      0,
0241                                      1,
0242                                      1,
0243                                      0,
0244                                      BlackPixel(disp, screen_P == -1 ? DefaultScreen(disp) : screen_P),
0245                                      BlackPixel(disp, screen_P == -1 ? DefaultScreen(disp) : screen_P));
0246     send_message_internal(root, message_P, BROADCAST_MASK, disp, a1, a2, win);
0247     XDestroyWindow(disp, win);
0248     return true;
0249 }
0250 #endif
0251 
0252 bool KXMessages::broadcastMessageX(xcb_connection_t *c, const char *msg_type_P, const QString &message, int screenNumber)
0253 {
0254     if (!c) {
0255         return false;
0256     }
0257     const QByteArray msg(msg_type_P);
0258     XcbAtom a2(c, msg);
0259     XcbAtom a1(c, msg + QByteArrayLiteral("_BEGIN"));
0260     const xcb_screen_t *screen = defaultScreen(c, screenNumber);
0261     if (!screen) {
0262         return false;
0263     }
0264     const xcb_window_t root = screen->root;
0265     const xcb_window_t win = xcb_generate_id(c);
0266     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);
0267     send_message_internal(root, message, c, a1, a2, win);
0268     xcb_destroy_window(c, win);
0269     return true;
0270 }
0271 
0272 #if 0 // currently unused
0273 void KXMessages::sendMessage(WId w_P, const char *msg_type_P, const QString &message_P)
0274 {
0275     Atom a2 = XInternAtom(QX11Info::display(), msg_type_P, false);
0276     Atom a1 = XInternAtom(QX11Info::display(), QByteArray(QByteArray(msg_type_P) + "_BEGIN").constData(), false);
0277     send_message_internal(w_P, message_P, 0, QX11Info::display(), a1, a2, d->handle->winId());
0278 }
0279 
0280 bool KXMessages::sendMessageX(Display *disp, WId w_P, const char *msg_type_P,
0281                               const QString &message_P)
0282 {
0283     if (disp == nullptr) {
0284         return false;
0285     }
0286     Atom a2 = XInternAtom(disp, msg_type_P, false);
0287     Atom a1 = XInternAtom(disp, QByteArray(QByteArray(msg_type_P) + "_BEGIN").constData(), false);
0288     Window win = XCreateSimpleWindow(disp, DefaultRootWindow(disp), 0, 0, 1, 1,
0289                                      0, BlackPixelOfScreen(DefaultScreenOfDisplay(disp)),
0290                                      BlackPixelOfScreen(DefaultScreenOfDisplay(disp)));
0291     send_message_internal(w_P, message_P, 0, disp, a1, a2, win);
0292     XDestroyWindow(disp, win);
0293     return true;
0294 }
0295 #endif
0296 
0297 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
0298 static void send_message_internal(WId w_P, const QString &msg_P, long mask_P, Display *disp, Atom atom1_P, Atom atom2_P, Window handle_P)
0299 {
0300     // qDebug() << "send_message_internal" << w_P << msg_P << mask_P << atom1_P << atom2_P << handle_P;
0301     unsigned int pos = 0;
0302     QByteArray msg = msg_P.toUtf8();
0303     const size_t len = msg.size();
0304     XEvent e;
0305     e.xclient.type = ClientMessage;
0306     e.xclient.message_type = atom1_P; // leading message
0307     e.xclient.display = disp;
0308     e.xclient.window = handle_P;
0309     e.xclient.format = 8;
0310     do {
0311         unsigned int i;
0312         for (i = 0; i < 20 && i + pos < len; ++i) {
0313             e.xclient.data.b[i] = msg[i + pos];
0314         }
0315         for (; i < 20; ++i) {
0316             e.xclient.data.b[i] = 0;
0317         }
0318         XSendEvent(disp, w_P, false, mask_P, &e);
0319         e.xclient.message_type = atom2_P; // following messages
0320         pos += i;
0321     } while (pos <= len);
0322     XFlush(disp);
0323 }
0324 #endif
0325 
0326 static void
0327 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)
0328 {
0329     unsigned int pos = 0;
0330     QByteArray msg = msg_P.toUtf8();
0331     const size_t len = msg.size();
0332 
0333     xcb_client_message_event_t event;
0334     event.response_type = XCB_CLIENT_MESSAGE;
0335     event.format = 8;
0336     event.sequence = 0;
0337     event.window = handle;
0338     event.type = leadingMessage;
0339 
0340     do {
0341         unsigned int i;
0342         for (i = 0; i < 20 && i + pos < len; ++i) {
0343             event.data.data8[i] = msg[i + pos];
0344         }
0345         for (; i < 20; ++i) {
0346             event.data.data8[i] = 0;
0347         }
0348         xcb_send_event(c, false, w, XCB_EVENT_MASK_PROPERTY_CHANGE, (const char *)&event);
0349         event.type = followingMessage;
0350         pos += i;
0351     } while (pos <= len);
0352 
0353     xcb_flush(c);
0354 }
0355 
0356 #include "moc_kxmessages.cpp"
0357 
0358 #endif