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 "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:
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  }
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 
43  ~XcbAtom()
44  {
45  if (!m_retrieved && m_cookie.sequence && m_connection) {
46  xcb_discard_reply(m_connection, m_cookie.sequence);
47  }
48  }
49 
50  operator xcb_atom_t()
51  {
52  getReply();
53  return m_atom;
54  }
55 
56  inline const QByteArray &name() const
57  {
58  return m_name;
59  }
60 
61  inline void setConnection(xcb_connection_t *c)
62  {
63  m_connection = c;
64  }
65 
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:
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:
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();
112  }
113  }
114  XcbAtom accept_atom1;
115  XcbAtom accept_atom2;
116  QMap<WId, QByteArray> incoming_messages;
118  KXMessages *q;
119  bool valid;
120  xcb_connection_t *connection;
121  xcb_window_t rootWindow;
122 
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 
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 
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 
191 KXMessages::~KXMessages()
192 {
193  delete d;
194 }
195 
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 
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)
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 
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)
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
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
QString name(const QVariant &location)
bool contains(const Key &key) const const
Sending string messages to other applications using the X Client Messages.
Definition: kxmessages.h:32
virtual bool event(QEvent *e)
QString fromUtf8(const char *str, int size)
const char * constData() const const
QCoreApplication * instance()
void installNativeEventFilter(QAbstractNativeEventFilter *filterObj)
void broadcastMessage(const char *msg_type, const QString &message, int screen=-1)
Broadcasts the given message with the given message type.
Definition: kxmessages.cpp:206
void gotMessage(const QString &message)
Emitted when a message was received.
char * data()
KXMessages(const char *accept_broadcast=nullptr, QObject *parent=nullptr)
Creates an instance which will receive X messages.
Definition: kxmessages.cpp:176
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:220
int remove(const Key &key)
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Oct 24 2021 22:41:47 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.