Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qxcbeventqueue.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#include "qxcbeventqueue.h"
4#include "qxcbconnection.h"
5
6#include <QtCore/QObject>
7#include <QtCore/QCoreApplication>
8#include <QtCore/QAbstractEventDispatcher>
9#include <QtCore/QMutex>
10#include <QtCore/QDebug>
11
13
14Q_CONSTINIT static QBasicMutex qAppExiting;
15Q_CONSTINIT static bool dispatcherOwnerDestructing = false;
16
46 : m_connection(connection)
47{
48 // When running test cases in auto tests, static variables are preserved
49 // between test function runs, even if Q*Application object is destroyed.
50 // Reset to default value to account for this.
52 qAddPostRoutine([]() {
55 });
56
57 // Lets init the list with one node, so we don't have to check for
58 // this special case in various places.
59 m_head = m_flushedTail = qXcbEventNodeFactory(nullptr);
60 m_tail.store(m_head, std::memory_order_release);
61
62 start();
63}
64
66{
67 if (isRunning()) {
68 sendCloseConnectionEvent();
69 wait();
70 }
71
73 while (xcb_generic_event_t *event = takeFirst(QEventLoop::AllEvents))
74 free(event);
75
76 if (m_head && m_head->fromHeap)
77 delete m_head; // the deferred node
78
79 qCDebug(lcQpaEventReader) << "nodes on heap:" << m_nodesOnHeap;
80}
81
82xcb_generic_event_t *QXcbEventQueue::takeFirst(QEventLoop::ProcessEventsFlags flags)
83{
84 // This is the level at which we were moving excluded user input events into
85 // separate queue in Qt 4 (see qeventdispatcher_x11.cpp). In this case
86 // QXcbEventQueue represents Xlib's internal event queue. In Qt 4, Xlib's
87 // event queue peeking APIs would not see these events anymore, the same way
88 // our peeking functions do not consider m_inputEvents. This design is
89 // intentional to keep the same behavior. We could do filtering directly on
90 // QXcbEventQueue, without the m_inputEvents, but it is not clear if it is
91 // needed by anyone who peeks at the native event queue.
92
93 bool excludeUserInputEvents = flags.testFlag(QEventLoop::ExcludeUserInputEvents);
94 if (excludeUserInputEvents) {
95 xcb_generic_event_t *event = nullptr;
96 while ((event = takeFirst())) {
97 if (m_connection->isUserInputEvent(event)) {
98 m_inputEvents << event;
99 continue;
100 }
101 break;
102 }
103 return event;
104 }
105
106 if (!m_inputEvents.isEmpty())
107 return m_inputEvents.takeFirst();
108 return takeFirst();
109}
110
111xcb_generic_event_t *QXcbEventQueue::takeFirst()
112{
113 if (isEmpty())
114 return nullptr;
115
116 xcb_generic_event_t *event = nullptr;
117 do {
118 event = m_head->event;
119 if (m_head == m_flushedTail) {
120 // defer dequeuing until next successful flush of events
121 if (event) // check if not cleared already by some filter
122 m_head->event = nullptr; // if not, clear it
123 } else {
124 dequeueNode();
125 if (!event)
126 continue; // consumed by filter or deferred node
127 }
128 } while (!isEmpty() && !event);
129
130 m_queueModified = m_peekerIndexCacheDirty = true;
131
132 return event;
133}
134
135void QXcbEventQueue::dequeueNode()
136{
137 QXcbEventNode *node = m_head;
138 m_head = m_head->next;
139 if (node->fromHeap)
140 delete node;
141 else
142 m_nodesRestored.fetch_add(1, std::memory_order_release);
143}
144
146{
147 m_flushedTail = m_tail.load(std::memory_order_acquire);
148}
149
150QXcbEventNode *QXcbEventQueue::qXcbEventNodeFactory(xcb_generic_event_t *event)
151{
152 static QXcbEventNode qXcbNodePool[PoolSize];
153
154 if (m_freeNodes == 0) // out of nodes, check if the main thread has released any
155 m_freeNodes = m_nodesRestored.exchange(0, std::memory_order_acquire);
156
157 if (m_freeNodes) {
158 m_freeNodes--;
159 if (m_poolIndex == PoolSize) {
160 // wrap back to the beginning, we always take and restore nodes in-order
161 m_poolIndex = 0;
162 }
163 QXcbEventNode *node = &qXcbNodePool[m_poolIndex++];
164 node->event = event;
165 node->next = nullptr;
166 return node;
167 }
168
169 // the main thread is not flushing events and thus the pool has become empty
170 auto node = new QXcbEventNode(event);
171 node->fromHeap = true;
172 qCDebug(lcQpaEventReader) << "[heap] " << m_nodesOnHeap++;
173 return node;
174}
175
177{
178 xcb_generic_event_t *event = nullptr;
179 xcb_connection_t *connection = m_connection->xcb_connection();
180 QXcbEventNode *tail = m_head;
181
182 auto enqueueEvent = [&tail, this](xcb_generic_event_t *event) {
183 if (!isCloseConnectionEvent(event)) {
184 tail->next = qXcbEventNodeFactory(event);
185 tail = tail->next;
186 m_tail.store(tail, std::memory_order_release);
187 } else {
188 free(event);
189 }
190 };
191
192 while (!m_closeConnectionDetected && (event = xcb_wait_for_event(connection))) {
193 // This lock can block only if there are users of waitForNewEvents().
194 // Currently only the clipboard implementation relies on it.
195 m_newEventsMutex.lock();
196 enqueueEvent(event);
197 while (!m_closeConnectionDetected && (event = xcb_poll_for_queued_event(connection)))
198 enqueueEvent(event);
199
200 m_newEventsCondition.wakeOne();
201 m_newEventsMutex.unlock();
203 }
204
205 if (!m_closeConnectionDetected) {
206 // Connection was terminated not by us. Wake up dispatcher, which will
207 // call processXcbEvents(), where we handle the connection errors via
208 // xcb_connection_has_error().
210 }
211}
212
214{
215 QMutexLocker locker(&qAppExiting);
217 // This thread can run before a dispatcher has been created,
218 // so check if it is ready.
221 }
222}
223
225{
226 const qint32 peekerId = m_peekerIdSource++;
227 m_peekerToNode.insert(peekerId, nullptr);
228 return peekerId;
229}
230
232{
233 const auto it = m_peekerToNode.constFind(peekerId);
234 if (it == m_peekerToNode.constEnd()) {
235 qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
236 return false;
237 }
238 m_peekerToNode.erase(it);
239 if (m_peekerToNode.isEmpty()) {
240 m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
241 m_peekerIndexCacheDirty = false;
242 }
243 return true;
244}
245
246bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
247 PeekOptions option, qint32 peekerId)
248{
249 const bool peekerIdProvided = peekerId != -1;
250 auto peekerToNodeIt = m_peekerToNode.find(peekerId);
251
252 if (peekerIdProvided && peekerToNodeIt == m_peekerToNode.end()) {
253 qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
254 return false;
255 }
256
257 const bool useCache = option.testFlag(PeekOption::PeekFromCachedIndex);
258 if (useCache && !peekerIdProvided) {
259 qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
260 return false;
261 }
262
263 if (peekerIdProvided && m_peekerIndexCacheDirty) {
264 for (auto &node : m_peekerToNode) // reset cache
265 node = nullptr;
266 m_peekerIndexCacheDirty = false;
267 }
268
270 if (isEmpty())
271 return false;
272
273 const auto startNode = [this, useCache, peekerToNodeIt]() -> QXcbEventNode * {
274 if (useCache) {
275 const QXcbEventNode *cachedNode = peekerToNodeIt.value();
276 if (!cachedNode)
277 return m_head; // cache was reset
278 if (cachedNode == m_flushedTail)
279 return nullptr; // no new events since the last call
280 return cachedNode->next;
281 }
282 return m_head;
283 }();
284
285 if (!startNode)
286 return false;
287
288 // A peeker may call QCoreApplication::processEvents(), which will cause
289 // QXcbConnection::processXcbEvents() to modify the queue we are currently
290 // looping through;
291 m_queueModified = false;
292 bool result = false;
293
294 QXcbEventNode *node = startNode;
295 do {
296 xcb_generic_event_t *event = node->event;
297 if (event && peeker(event, peekerData)) {
298 result = true;
299 break;
300 }
301 if (node == m_flushedTail)
302 break;
303 node = node->next;
304 } while (!m_queueModified);
305
306 // Update the cached index if the queue was not modified, and hence the
307 // cache is still valid.
308 if (peekerIdProvided && node != startNode && !m_queueModified) {
309 // Before updating, make sure that a peeker callback did not remove
310 // the peeker id.
311 peekerToNodeIt = m_peekerToNode.find(peekerId);
312 if (peekerToNodeIt != m_peekerToNode.end())
313 *peekerToNodeIt = node; // id still in the cache, update node
314 }
315
316 return result;
317}
318
320 unsigned long time)
321{
322 QMutexLocker locker(&m_newEventsMutex);
324 if (sinceFlushedTail != m_flushedTail)
325 return;
326 m_newEventsCondition.wait(&m_newEventsMutex, time);
327}
328
329void QXcbEventQueue::sendCloseConnectionEvent() const
330{
331 // A hack to close XCB connection. Apparently XCB does not have any APIs for this?
332 xcb_client_message_event_t event;
333 memset(&event, 0, sizeof(event));
334
335 xcb_connection_t *c = m_connection->xcb_connection();
336 const xcb_window_t window = xcb_generate_id(c);
337 xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_connection->setup());
338 xcb_screen_t *screen = it.data;
339 xcb_create_window(c, XCB_COPY_FROM_PARENT,
340 window, screen->root,
341 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
342 screen->root_visual, 0, nullptr);
343
344 event.response_type = XCB_CLIENT_MESSAGE;
345 event.format = 32;
346 event.sequence = 0;
347 event.window = window;
348 event.type = m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION);
349 event.data.data32[0] = 0;
350
351 xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
352 xcb_destroy_window(c, window);
353 xcb_flush(c);
354}
355
356bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
357{
358 if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
359 auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
360 if (clientMessage->type == m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION))
361 m_closeConnectionDetected = true;
362 }
363 return m_closeConnectionDetected;
364}
365
367
368#include "moc_qxcbeventqueue.cpp"
static QAbstractEventDispatcher * eventDispatcher()
Returns a pointer to the event dispatcher object for the main thread.
@ ExcludeUserInputEvents
Definition qeventloop.h:27
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1299
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1219
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1291
iterator erase(const_iterator it)
Definition qhash.h:1233
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1216
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:928
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
bool isEmpty() const noexcept
Definition qlist.h:401
value_type takeFirst()
Definition qlist.h:566
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qmutex.h:281
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
bool isRunning() const
Definition qthread.cpp:1064
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
@ Atom_QT_CLOSE_CONNECTION
Definition qxcbatom.h:56
xcb_connection_t * xcb_connection() const
const xcb_setup_t * setup() const
xcb_atom_t atom(QXcbAtom::Atom qatom) const
bool isUserInputEvent(xcb_generic_event_t *event) const
bool(*)(xcb_generic_event_t *event, void *peekerData) PeekerCallback
bool removePeekerId(qint32 peekerId)
qint32 generatePeekerId()
QXcbEventQueue(QXcbConnection *connection)
bool isEmpty() const
void run() override
xcb_generic_event_t * takeFirst()
void waitForNewEvents(const QXcbEventNode *sinceFlushedTail, unsigned long time=(std::numeric_limits< unsigned long >::max)())
bool peekEventQueue(PeekerCallback peeker, void *peekerData=nullptr, PeekOptions option=PeekDefault, qint32 peekerId=-1)
QSet< QString >::iterator it
Combined button and popup list for selecting options.
void qAddPostRoutine(QtCleanUpFunction p)
DBusConnection * connection
#define qCWarning(category,...)
#define qCDebug(category,...)
GLbitfield flags
GLuint start
struct _cl_event * event
const GLubyte * c
GLuint64EXT * result
[6]
GLuint GLenum option
QScreen * screen
[1]
Definition main.cpp:29
int qint32
Definition qtypes.h:49
static Q_CONSTINIT bool dispatcherOwnerDestructing
QT_BEGIN_NAMESPACE static Q_CONSTINIT QBasicMutex qAppExiting
aWidget window() -> setWindowTitle("New Window Title")
[2]
xcb_generic_event_t * event
QXcbEventNode * next