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 "cptr_p.h"
10#include "kxutils_p.h"
11#include "kxcbevent_p.h"
12
13#if KWINDOWSYSTEM_HAVE_X11
14
15#include <QAbstractNativeEventFilter>
16#include <QCoreApplication>
17#include <QDebug>
18#include <QWindow> // WId
19
20#include <X11/Xlib.h>
21
22#include <private/qtx11extras_p.h>
23
24class XcbAtom
25{
26public:
27 explicit XcbAtom(const QByteArray &name, bool onlyIfExists = false)
28 : m_name(name)
29 , m_atom(XCB_ATOM_NONE)
30 , m_connection(nullptr)
31 , m_retrieved(false)
32 , m_onlyIfExists(onlyIfExists)
33 {
34 m_cookie.sequence = 0;
35 }
36 explicit XcbAtom(xcb_connection_t *c, const QByteArray &name, bool onlyIfExists = false)
37 : m_name(name)
38 , m_atom(XCB_ATOM_NONE)
39 , m_cookie(xcb_intern_atom_unchecked(c, only_if_exists: onlyIfExists, name_len: name.length(), name: name.constData()))
40 , m_connection(c)
41 , m_retrieved(false)
42 , m_onlyIfExists(onlyIfExists)
43 {
44 }
45
46 ~XcbAtom()
47 {
48 if (!m_retrieved && m_cookie.sequence && m_connection) {
49 xcb_discard_reply(c: m_connection, sequence: m_cookie.sequence);
50 }
51 }
52
53 operator xcb_atom_t()
54 {
55 getReply();
56 return m_atom;
57 }
58
59 inline const QByteArray &name() const
60 {
61 return m_name;
62 }
63
64 inline void setConnection(xcb_connection_t *c)
65 {
66 m_connection = c;
67 }
68
69 inline void fetch()
70 {
71 if (!m_connection || m_name.isEmpty()) {
72 return;
73 }
74 m_cookie = xcb_intern_atom_unchecked(c: m_connection, only_if_exists: m_onlyIfExists, name_len: m_name.length(), name: m_name.constData());
75 }
76
77private:
78 void getReply()
79 {
80 if (m_retrieved || !m_cookie.sequence || !m_connection) {
81 return;
82 }
83 UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(c: m_connection, cookie: m_cookie, e: nullptr));
84 if (reply) {
85 m_atom = reply->atom;
86 }
87 m_retrieved = true;
88 }
89 QByteArray m_name;
90 xcb_atom_t m_atom;
91 xcb_intern_atom_cookie_t m_cookie;
92 xcb_connection_t *m_connection;
93 bool m_retrieved;
94 bool m_onlyIfExists;
95};
96
97class KXMessagesPrivate : public QAbstractNativeEventFilter
98{
99public:
100 KXMessagesPrivate(KXMessages *parent, const char *acceptBroadcast, xcb_connection_t *c, xcb_window_t root)
101 : accept_atom1(acceptBroadcast ? QByteArray(acceptBroadcast) + QByteArrayLiteral("_BEGIN") : QByteArray())
102 , accept_atom2(acceptBroadcast ? QByteArray(acceptBroadcast) : QByteArray())
103 , handle(new QWindow)
104 , q(parent)
105 , valid(c)
106 , connection(c)
107 , rootWindow(root)
108 {
109 if (acceptBroadcast) {
110 accept_atom1.setConnection(c);
111 accept_atom1.fetch();
112 accept_atom2.setConnection(c);
113 accept_atom2.fetch();
114 QCoreApplication::instance()->installNativeEventFilter(filterObj: this);
115 }
116 }
117 XcbAtom accept_atom1;
118 XcbAtom accept_atom2;
119 QMap<WId, QByteArray> incoming_messages;
120 std::unique_ptr<QWindow> handle;
121 KXMessages *q;
122 bool valid;
123 xcb_connection_t *connection;
124 xcb_window_t rootWindow;
125
126 bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override
127 {
128 // A faster comparison than eventType != "xcb_generic_event_t"
129 if (eventType[0] != 'x') {
130 return false;
131 }
132 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
133 uint response_type = event->response_type & ~0x80;
134 if (response_type != XCB_CLIENT_MESSAGE) {
135 return false;
136 }
137 xcb_client_message_event_t *cm_event = reinterpret_cast<xcb_client_message_event_t *>(event);
138 if (cm_event->format != 8) {
139 return false;
140 }
141 if (cm_event->type != accept_atom1 && cm_event->type != accept_atom2) {
142 return false;
143 }
144 char buf[21]; // can't be longer
145 // Copy the data in order to null-terminate it
146 qstrncpy(dst: buf, src: reinterpret_cast<char *>(cm_event->data.data8), len: 21);
147 // qDebug() << cm_event->window << "buf=\"" << buf << "\" atom=" << (cm_event->type == accept_atom1 ? "atom1" : "atom2");
148 if (incoming_messages.contains(key: cm_event->window)) {
149 if (cm_event->type == accept_atom1)
150 // two different messages on the same window at the same time shouldn't happen anyway
151 {
152 incoming_messages[cm_event->window] = QByteArray();
153 }
154 incoming_messages[cm_event->window] += buf;
155 } else {
156 if (cm_event->type == accept_atom2) {
157 return false; // middle of message, but we don't have the beginning
158 }
159 incoming_messages[cm_event->window] = buf;
160 }
161 if (strlen(s: buf) < 20) { // last message fragment
162 Q_EMIT q->gotMessage(message: QString::fromUtf8(utf8: incoming_messages[cm_event->window].constData()));
163 incoming_messages.remove(key: cm_event->window);
164 }
165 return false; // lets other KXMessages instances get the event too
166 }
167};
168
169static void
170send_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);
171
172KXMessages::KXMessages(const char *accept_broadcast_P, QObject *parent_P)
173 : QObject(parent_P)
174 , d(new KXMessagesPrivate(this,
175 accept_broadcast_P,
176 QX11Info::isPlatformX11() ? QX11Info::connection() : nullptr,
177 QX11Info::isPlatformX11() ? QX11Info::appRootWindow() : 0))
178{
179}
180
181KXMessages::KXMessages(xcb_connection_t *connection, xcb_window_t rootWindow, const char *accept_broadcast, QObject *parent)
182 : QObject(parent)
183 , d(new KXMessagesPrivate(this, accept_broadcast, connection, rootWindow))
184{
185}
186
187KXMessages::~KXMessages()
188{
189 delete d;
190}
191
192static xcb_screen_t *defaultScreen(xcb_connection_t *c, int screen)
193{
194 for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(R: xcb_get_setup(c)); it.rem; --screen, xcb_screen_next(i: &it)) {
195 if (screen == 0) {
196 return it.data;
197 }
198 }
199 return nullptr;
200}
201
202void KXMessages::broadcastMessage(const char *msg_type_P, const QString &message_P, int screen_P)
203{
204 if (!d->valid) {
205 qWarning() << "KXMessages used on non-X11 platform! This is an application bug.";
206 return;
207 }
208 const QByteArray msg(msg_type_P);
209 XcbAtom a2(d->connection, msg);
210 XcbAtom a1(d->connection, msg + QByteArrayLiteral("_BEGIN"));
211 xcb_window_t root = screen_P == -1 ? d->rootWindow : defaultScreen(c: d->connection, screen: screen_P)->root;
212 send_message_internal(w: root, msg: message_P, c: d->connection, leadingMessage: a1, followingMessage: a2, handle: d->handle->winId());
213}
214
215bool KXMessages::broadcastMessageX(xcb_connection_t *c, const char *msg_type_P, const QString &message, int screenNumber)
216{
217 if (!c) {
218 return false;
219 }
220 const QByteArray msg(msg_type_P);
221 XcbAtom a2(c, msg);
222 XcbAtom a1(c, msg + QByteArrayLiteral("_BEGIN"));
223 const xcb_screen_t *screen = defaultScreen(c, screen: screenNumber);
224 if (!screen) {
225 return false;
226 }
227 const xcb_window_t root = screen->root;
228 const xcb_window_t win = xcb_generate_id(c);
229 xcb_create_window(c, XCB_COPY_FROM_PARENT, wid: win, parent: root, x: 0, y: 0, width: 1, height: 1, border_width: 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, value_mask: 0, value_list: nullptr);
230 send_message_internal(w: root, msg: message, c, leadingMessage: a1, followingMessage: a2, handle: win);
231 xcb_destroy_window(c, window: win);
232 return true;
233}
234
235static void
236send_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)
237{
238 unsigned int pos = 0;
239 QByteArray msg = msg_P.toUtf8();
240 const size_t len = msg.size();
241
242 KXcbEvent<xcb_client_message_event_t> event;
243 event.response_type = XCB_CLIENT_MESSAGE;
244 event.format = 8;
245 event.sequence = 0;
246 event.window = handle;
247 event.type = leadingMessage;
248
249 do {
250 unsigned int i;
251 for (i = 0; i < 20 && i + pos < len; ++i) {
252 event.data.data8[i] = msg[i + pos];
253 }
254 for (; i < 20; ++i) {
255 event.data.data8[i] = 0;
256 }
257 xcb_send_event(c, propagate: false, destination: w, event_mask: XCB_EVENT_MASK_PROPERTY_CHANGE, event: event.buffer());
258 event.type = followingMessage;
259 pos += i;
260 } while (pos <= len);
261
262 xcb_flush(c);
263}
264
265#endif
266
267#include "moc_kxmessages.cpp"
268

source code of kwindowsystem/src/platforms/xcb/kxmessages.cpp