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