KWindowSystem

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

KDE's Doxygen guidelines are available online.