1 /*
2     SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org>
3     SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
4 
5     SPDX-License-Identifier: MIT
6 */
7 
8 #include "kxmessages.h"
9 #include "kxutils_p.h"
10 
11 #if KWINDOWSYSTEM_HAVE_X11
12 
13 #include <QAbstractNativeEventFilter>
14 #include <QCoreApplication>
15 #include <QDebug>
16 #include <QWindow> // WId
17 
18 #include <X11/Xlib.h>
19 #include <qx11info_x11.h>
20 
21 class XcbAtom
22 {
23 public:
XcbAtom(const QByteArray & name,bool onlyIfExists=false)24     explicit XcbAtom(const QByteArray &name, bool onlyIfExists = false)
25         : m_name(name)
26         , m_atom(XCB_ATOM_NONE)
27         , m_connection(nullptr)
28         , m_retrieved(false)
29         , m_onlyIfExists(onlyIfExists)
30     {
31         m_cookie.sequence = 0;
32     }
XcbAtom(xcb_connection_t * c,const QByteArray & name,bool onlyIfExists=false)33     explicit XcbAtom(xcb_connection_t *c, const QByteArray &name, bool onlyIfExists = false)
34         : m_name(name)
35         , m_atom(XCB_ATOM_NONE)
36         , m_cookie(xcb_intern_atom_unchecked(c, onlyIfExists, name.length(), name.constData()))
37         , m_connection(c)
38         , m_retrieved(false)
39         , m_onlyIfExists(onlyIfExists)
40     {
41     }
42 
~XcbAtom()43     ~XcbAtom()
44     {
45         if (!m_retrieved && m_cookie.sequence && m_connection) {
46             xcb_discard_reply(m_connection, m_cookie.sequence);
47         }
48     }
49 
operator xcb_atom_t()50     operator xcb_atom_t()
51     {
52         getReply();
53         return m_atom;
54     }
55 
name() const56     inline const QByteArray &name() const
57     {
58         return m_name;
59     }
60 
setConnection(xcb_connection_t * c)61     inline void setConnection(xcb_connection_t *c)
62     {
63         m_connection = c;
64     }
65 
fetch()66     inline void fetch()
67     {
68         if (!m_connection || m_name.isEmpty()) {
69             return;
70         }
71         m_cookie = xcb_intern_atom_unchecked(m_connection, m_onlyIfExists, m_name.length(), m_name.constData());
72     }
73 
74 private:
getReply()75     void getReply()
76     {
77         if (m_retrieved || !m_cookie.sequence || !m_connection) {
78             return;
79         }
80         KXUtils::ScopedCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(m_connection, m_cookie, nullptr));
81         if (!reply.isNull()) {
82             m_atom = reply->atom;
83         }
84         m_retrieved = true;
85     }
86     QByteArray m_name;
87     xcb_atom_t m_atom;
88     xcb_intern_atom_cookie_t m_cookie;
89     xcb_connection_t *m_connection;
90     bool m_retrieved;
91     bool m_onlyIfExists;
92 };
93 
94 class KXMessagesPrivate : public QAbstractNativeEventFilter
95 {
96 public:
KXMessagesPrivate(KXMessages * parent,const char * acceptBroadcast,xcb_connection_t * c,xcb_window_t root)97     KXMessagesPrivate(KXMessages *parent, const char *acceptBroadcast, xcb_connection_t *c, xcb_window_t root)
98         : accept_atom1(acceptBroadcast ? QByteArray(acceptBroadcast) + QByteArrayLiteral("_BEGIN") : QByteArray())
99         , accept_atom2(acceptBroadcast ? QByteArray(acceptBroadcast) : QByteArray())
100         , handle(new QWindow)
101         , q(parent)
102         , valid(c)
103         , connection(c)
104         , rootWindow(root)
105     {
106         if (acceptBroadcast) {
107             accept_atom1.setConnection(c);
108             accept_atom1.fetch();
109             accept_atom2.setConnection(c);
110             accept_atom2.fetch();
111             QCoreApplication::instance()->installNativeEventFilter(this);
112         }
113     }
114     XcbAtom accept_atom1;
115     XcbAtom accept_atom2;
116     QMap<WId, QByteArray> incoming_messages;
117     QScopedPointer<QWindow> handle;
118     KXMessages *q;
119     bool valid;
120     xcb_connection_t *connection;
121     xcb_window_t rootWindow;
122 
nativeEventFilter(const QByteArray & eventType,void * message,long * result)123     bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override
124     {
125         Q_UNUSED(result);
126         // A faster comparison than eventType != "xcb_generic_event_t"
127         if (eventType[0] != 'x') {
128             return false;
129         }
130         xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
131         uint response_type = event->response_type & ~0x80;
132         if (response_type != XCB_CLIENT_MESSAGE) {
133             return false;
134         }
135         xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
136         if (cm_event->format != 8) {
137             return false;
138         }
139         if (cm_event->type != accept_atom1 && cm_event->type != accept_atom2) {
140             return false;
141         }
142         char buf[21]; // can't be longer
143         // Copy the data in order to null-terminate it
144         qstrncpy(buf, reinterpret_cast<char *>(cm_event->data.data8), 21);
145         // qDebug() << cm_event->window << "buf=\"" << buf << "\" atom=" << (cm_event->type == accept_atom1 ? "atom1" : "atom2");
146         if (incoming_messages.contains(cm_event->window)) {
147             if (cm_event->type == accept_atom1)
148             // two different messages on the same window at the same time shouldn't happen anyway
149             {
150                 incoming_messages[cm_event->window] = QByteArray();
151             }
152             incoming_messages[cm_event->window] += buf;
153         } else {
154             if (cm_event->type == accept_atom2) {
155                 return false; // middle of message, but we don't have the beginning
156             }
157             incoming_messages[cm_event->window] = buf;
158         }
159         if (strlen(buf) < 20) { // last message fragment
160             Q_EMIT q->gotMessage(QString::fromUtf8(incoming_messages[cm_event->window].constData()));
161             incoming_messages.remove(cm_event->window);
162         }
163         return false; // lets other KXMessages instances get the event too
164     }
165 };
166 
167 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
168 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);
169 // for broadcasting
170 static const long BROADCAST_MASK = PropertyChangeMask;
171 // CHECKME
172 #endif
173 static void
174 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);
175 
KXMessages(const char * accept_broadcast_P,QObject * parent_P)176 KXMessages::KXMessages(const char *accept_broadcast_P, QObject *parent_P)
177     : QObject(parent_P)
178     , d(new KXMessagesPrivate(this,
179                               accept_broadcast_P,
180                               QX11Info::isPlatformX11() ? QX11Info::connection() : nullptr,
181                               QX11Info::isPlatformX11() ? QX11Info::appRootWindow() : 0))
182 {
183 }
184 
KXMessages(xcb_connection_t * connection,xcb_window_t rootWindow,const char * accept_broadcast,QObject * parent)185 KXMessages::KXMessages(xcb_connection_t *connection, xcb_window_t rootWindow, const char *accept_broadcast, QObject *parent)
186     : QObject(parent)
187     , d(new KXMessagesPrivate(this, accept_broadcast, connection, rootWindow))
188 {
189 }
190 
~KXMessages()191 KXMessages::~KXMessages()
192 {
193     delete d;
194 }
195 
defaultScreen(xcb_connection_t * c,int screen)196 static xcb_screen_t *defaultScreen(xcb_connection_t *c, int screen)
197 {
198     for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(c)); it.rem; --screen, xcb_screen_next(&it)) {
199         if (screen == 0) {
200             return it.data;
201         }
202     }
203     return nullptr;
204 }
205 
broadcastMessage(const char * msg_type_P,const QString & message_P,int screen_P)206 void KXMessages::broadcastMessage(const char *msg_type_P, const QString &message_P, int screen_P)
207 {
208     if (!d->valid) {
209         qWarning() << "KXMessages used on non-X11 platform! This is an application bug.";
210         return;
211     }
212     const QByteArray msg(msg_type_P);
213     XcbAtom a2(d->connection, msg);
214     XcbAtom a1(d->connection, msg + QByteArrayLiteral("_BEGIN"));
215     xcb_window_t root = screen_P == -1 ? d->rootWindow : defaultScreen(d->connection, screen_P)->root;
216     send_message_internal(root, message_P, d->connection, a1, a2, d->handle->winId());
217 }
218 
219 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
broadcastMessageX(Display * disp,const char * msg_type_P,const QString & message_P,int screen_P)220 bool KXMessages::broadcastMessageX(Display *disp, const char *msg_type_P, const QString &message_P, int screen_P)
221 {
222     if (disp == nullptr) {
223         return false;
224     }
225     Atom a2 = XInternAtom(disp, msg_type_P, false);
226     Atom a1 = XInternAtom(disp, QByteArray(QByteArray(msg_type_P) + "_BEGIN").constData(), false);
227     Window root = screen_P == -1 ? DefaultRootWindow(disp) : RootWindow(disp, screen_P);
228     Window win = XCreateSimpleWindow(disp,
229                                      root,
230                                      0,
231                                      0,
232                                      1,
233                                      1,
234                                      0,
235                                      BlackPixel(disp, screen_P == -1 ? DefaultScreen(disp) : screen_P),
236                                      BlackPixel(disp, screen_P == -1 ? DefaultScreen(disp) : screen_P));
237     send_message_internal(root, message_P, BROADCAST_MASK, disp, a1, a2, win);
238     XDestroyWindow(disp, win);
239     return true;
240 }
241 #endif
242 
broadcastMessageX(xcb_connection_t * c,const char * msg_type_P,const QString & message,int screenNumber)243 bool KXMessages::broadcastMessageX(xcb_connection_t *c, const char *msg_type_P, const QString &message, int screenNumber)
244 {
245     if (!c) {
246         return false;
247     }
248     const QByteArray msg(msg_type_P);
249     XcbAtom a2(c, msg);
250     XcbAtom a1(c, msg + QByteArrayLiteral("_BEGIN"));
251     const xcb_screen_t *screen = defaultScreen(c, screenNumber);
252     if (!screen) {
253         return false;
254     }
255     const xcb_window_t root = screen->root;
256     const xcb_window_t win = xcb_generate_id(c);
257     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);
258     send_message_internal(root, message, c, a1, a2, win);
259     xcb_destroy_window(c, win);
260     return true;
261 }
262 
263 #if 0 // currently unused
264 void KXMessages::sendMessage(WId w_P, const char *msg_type_P, const QString &message_P)
265 {
266     Atom a2 = XInternAtom(QX11Info::display(), msg_type_P, false);
267     Atom a1 = XInternAtom(QX11Info::display(), QByteArray(QByteArray(msg_type_P) + "_BEGIN").constData(), false);
268     send_message_internal(w_P, message_P, 0, QX11Info::display(), a1, a2, d->handle->winId());
269 }
270 
271 bool KXMessages::sendMessageX(Display *disp, WId w_P, const char *msg_type_P,
272                               const QString &message_P)
273 {
274     if (disp == nullptr) {
275         return false;
276     }
277     Atom a2 = XInternAtom(disp, msg_type_P, false);
278     Atom a1 = XInternAtom(disp, QByteArray(QByteArray(msg_type_P) + "_BEGIN").constData(), false);
279     Window win = XCreateSimpleWindow(disp, DefaultRootWindow(disp), 0, 0, 1, 1,
280                                      0, BlackPixelOfScreen(DefaultScreenOfDisplay(disp)),
281                                      BlackPixelOfScreen(DefaultScreenOfDisplay(disp)));
282     send_message_internal(w_P, message_P, 0, disp, a1, a2, win);
283     XDestroyWindow(disp, win);
284     return true;
285 }
286 #endif
287 
288 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 18)
send_message_internal(WId w_P,const QString & msg_P,long mask_P,Display * disp,Atom atom1_P,Atom atom2_P,Window handle_P)289 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)
290 {
291     // qDebug() << "send_message_internal" << w_P << msg_P << mask_P << atom1_P << atom2_P << handle_P;
292     unsigned int pos = 0;
293     QByteArray msg = msg_P.toUtf8();
294     unsigned int len = strlen(msg.constData());
295     XEvent e;
296     e.xclient.type = ClientMessage;
297     e.xclient.message_type = atom1_P; // leading message
298     e.xclient.display = disp;
299     e.xclient.window = handle_P;
300     e.xclient.format = 8;
301     do {
302         unsigned int i;
303         for (i = 0; i < 20 && i + pos <= len; ++i) {
304             e.xclient.data.b[i] = msg[i + pos];
305         }
306         XSendEvent(disp, w_P, false, mask_P, &e);
307         e.xclient.message_type = atom2_P; // following messages
308         pos += i;
309     } while (pos <= len);
310     XFlush(disp);
311 }
312 #endif
313 
314 static void
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)315 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)
316 {
317     unsigned int pos = 0;
318     QByteArray msg = msg_P.toUtf8();
319     const size_t len = strlen(msg.constData());
320 
321     xcb_client_message_event_t event;
322     event.response_type = XCB_CLIENT_MESSAGE;
323     event.format = 8;
324     event.sequence = 0;
325     event.window = handle;
326     event.type = leadingMessage;
327 
328     do {
329         unsigned int i;
330         for (i = 0; i < 20 && i + pos <= len; ++i) {
331             event.data.data8[i] = msg[i + pos];
332         }
333         for (unsigned int j = i; j < 20; ++j) {
334             event.data.data8[j] = 0;
335         }
336         xcb_send_event(c, false, w, XCB_EVENT_MASK_PROPERTY_CHANGE, (const char *)&event);
337         event.type = followingMessage;
338         pos += i;
339     } while (pos <= len);
340 
341     xcb_flush(c);
342 }
343 
344 #endif
345