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#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, onlyIfExists, name.length(), 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(m_connection, 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(m_connection, m_onlyIfExists, m_name.length(), 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(m_connection, m_cookie, 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();
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(buf, reinterpret_cast<char *>(cm_event->data.data8), 21);
147 // qDebug() << cm_event->window << "buf=\"" << buf << "\" atom=" << (cm_event->type == accept_atom1 ? "atom1" : "atom2");
148 if (incoming_messages.contains(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(buf) < 20) { // last message fragment
162 Q_EMIT q->gotMessage(QString::fromUtf8(incoming_messages[cm_event->window].constData()));
163 incoming_messages.remove(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(xcb_get_setup(c)); it.rem; --screen, xcb_screen_next(&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(d->connection, screen_P)->root;
212 send_message_internal(root, message_P, d->connection, a1, a2, 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, 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, win, root, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, 0, nullptr);
230 send_message_internal(root, message, c, a1, a2, win);
231 xcb_destroy_window(c, 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, false, w, XCB_EVENT_MASK_PROPERTY_CHANGE, 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"
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(StandardAction id)
const char * constData() const const
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 Fri Oct 11 2024 12:13:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.