KWindowSystem

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

KDE's Doxygen guidelines are available online.