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