1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39#include "qxcbeventqueue.h"
40#include "qxcbconnection.h"
41
42#include <QtCore/QObject>
43#include <QtCore/QCoreApplication>
44#include <QtCore/QAbstractEventDispatcher>
45#include <QtCore/QMutex>
46#include <QtCore/QDebug>
47
48QT_BEGIN_NAMESPACE
49
50static QBasicMutex qAppExiting;
51static bool dispatcherOwnerDestructing = false;
52
53/*!
54 \class QXcbEventQueue
55 \internal
56
57 Lock-free event passing:
58
59 The lock-free solution uses a singly-linked list to pass events from the
60 reader thread to the main thread. An atomic operation is used to sync the
61 tail node of the list between threads. The reader thread takes special care
62 when accessing the tail node. It does not dequeue the last node and does not
63 access (read or write) the tail node's 'next' member. This lets the reader
64 add more items at the same time as the main thread is dequeuing nodes from
65 the head. A custom linked list implementation is used, because QLinkedList
66 does not have any thread-safety guarantees and the custom list is more
67 lightweight - no reference counting, back links, etc.
68
69 Memory management:
70
71 In a normally functioning application, XCB plugin won't buffer more than few
72 batches of events, couple events per batch. Instead of constantly calling
73 new / delete, we can create a pool of nodes that we reuse. The main thread
74 uses an atomic operation to sync how many nodes have been restored (available
75 for reuse). If at some point a user application will block the main thread
76 for a long time, we might run out of nodes in the pool. Then we create nodes
77 on a heap. These will be automatically "garbage collected" out of the linked
78 list, once the main thread stops blocking.
79*/
80
81QXcbEventQueue::QXcbEventQueue(QXcbConnection *connection)
82 : m_connection(connection)
83{
84 // When running test cases in auto tests, static variables are preserved
85 // between test function runs, even if Q*Application object is destroyed.
86 // Reset to default value to account for this.
87 dispatcherOwnerDestructing = false;
88 qAddPostRoutine([]() {
89 QMutexLocker locker(&qAppExiting);
90 dispatcherOwnerDestructing = true;
91 });
92
93 // Lets init the list with one node, so we don't have to check for
94 // this special case in various places.
95 m_head = m_flushedTail = qXcbEventNodeFactory(nullptr);
96 m_tail.store(m_head, std::memory_order_release);
97
98 start();
99}
100
101QXcbEventQueue::~QXcbEventQueue()
102{
103 if (isRunning()) {
104 sendCloseConnectionEvent();
105 wait();
106 }
107
108 flushBufferedEvents();
109 while (xcb_generic_event_t *event = takeFirst(QEventLoop::AllEvents))
110 free(event);
111
112 if (m_head && m_head->fromHeap)
113 delete m_head; // the deferred node
114
115 qCDebug(lcQpaEventReader) << "nodes on heap:" << m_nodesOnHeap;
116}
117
118xcb_generic_event_t *QXcbEventQueue::takeFirst(QEventLoop::ProcessEventsFlags flags)
119{
120 // This is the level at which we were moving excluded user input events into
121 // separate queue in Qt 4 (see qeventdispatcher_x11.cpp). In this case
122 // QXcbEventQueue represents Xlib's internal event queue. In Qt 4, Xlib's
123 // event queue peeking APIs would not see these events anymore, the same way
124 // our peeking functions do not consider m_inputEvents. This design is
125 // intentional to keep the same behavior. We could do filtering directly on
126 // QXcbEventQueue, without the m_inputEvents, but it is not clear if it is
127 // needed by anyone who peeks at the native event queue.
128
129 bool excludeUserInputEvents = flags.testFlag(QEventLoop::ExcludeUserInputEvents);
130 if (excludeUserInputEvents) {
131 xcb_generic_event_t *event = nullptr;
132 while ((event = takeFirst())) {
133 if (m_connection->isUserInputEvent(event)) {
134 m_inputEvents << event;
135 continue;
136 }
137 break;
138 }
139 return event;
140 }
141
142 if (!m_inputEvents.isEmpty())
143 return m_inputEvents.takeFirst();
144 return takeFirst();
145}
146
147xcb_generic_event_t *QXcbEventQueue::takeFirst()
148{
149 if (isEmpty())
150 return nullptr;
151
152 xcb_generic_event_t *event = nullptr;
153 do {
154 event = m_head->event;
155 if (m_head == m_flushedTail) {
156 // defer dequeuing until next successful flush of events
157 if (event) // check if not cleared already by some filter
158 m_head->event = nullptr; // if not, clear it
159 } else {
160 dequeueNode();
161 if (!event)
162 continue; // consumed by filter or deferred node
163 }
164 } while (!isEmpty() && !event);
165
166 m_queueModified = m_peekerIndexCacheDirty = true;
167
168 return event;
169}
170
171void QXcbEventQueue::dequeueNode()
172{
173 QXcbEventNode *node = m_head;
174 m_head = m_head->next;
175 if (node->fromHeap)
176 delete node;
177 else
178 m_nodesRestored.fetch_add(1, std::memory_order_release);
179}
180
181void QXcbEventQueue::flushBufferedEvents()
182{
183 m_flushedTail = m_tail.load(std::memory_order_acquire);
184}
185
186QXcbEventNode *QXcbEventQueue::qXcbEventNodeFactory(xcb_generic_event_t *event)
187{
188 static QXcbEventNode qXcbNodePool[PoolSize];
189
190 if (m_freeNodes == 0) // out of nodes, check if the main thread has released any
191 m_freeNodes = m_nodesRestored.exchange(0, std::memory_order_acquire);
192
193 if (m_freeNodes) {
194 m_freeNodes--;
195 if (m_poolIndex == PoolSize) {
196 // wrap back to the beginning, we always take and restore nodes in-order
197 m_poolIndex = 0;
198 }
199 QXcbEventNode *node = &qXcbNodePool[m_poolIndex++];
200 node->event = event;
201 node->next = nullptr;
202 return node;
203 }
204
205 // the main thread is not flushing events and thus the pool has become empty
206 auto node = new QXcbEventNode(event);
207 node->fromHeap = true;
208 qCDebug(lcQpaEventReader) << "[heap] " << m_nodesOnHeap++;
209 return node;
210}
211
212void QXcbEventQueue::run()
213{
214 xcb_generic_event_t *event = nullptr;
215 xcb_connection_t *connection = m_connection->xcb_connection();
216 QXcbEventNode *tail = m_head;
217
218 auto enqueueEvent = [&tail, this](xcb_generic_event_t *event) {
219 if (!isCloseConnectionEvent(event)) {
220 tail->next = qXcbEventNodeFactory(event);
221 tail = tail->next;
222 m_tail.store(tail, std::memory_order_release);
223 } else {
224 free(event);
225 }
226 };
227
228 while (!m_closeConnectionDetected && (event = xcb_wait_for_event(connection))) {
229 m_newEventsMutex.lock();
230 enqueueEvent(event);
231 while (!m_closeConnectionDetected && (event = xcb_poll_for_queued_event(connection)))
232 enqueueEvent(event);
233
234 m_newEventsCondition.wakeOne();
235 m_newEventsMutex.unlock();
236 wakeUpDispatcher();
237 }
238
239 if (!m_closeConnectionDetected) {
240 // Connection was terminated not by us. Wake up dispatcher, which will
241 // call processXcbEvents(), where we handle the connection errors via
242 // xcb_connection_has_error().
243 wakeUpDispatcher();
244 }
245}
246
247void QXcbEventQueue::wakeUpDispatcher()
248{
249 QMutexLocker locker(&qAppExiting);
250 if (!dispatcherOwnerDestructing) {
251 // This thread can run before a dispatcher has been created,
252 // so check if it is ready.
253 if (QCoreApplication::eventDispatcher())
254 QCoreApplication::eventDispatcher()->wakeUp();
255 }
256}
257
258qint32 QXcbEventQueue::generatePeekerId()
259{
260 const qint32 peekerId = m_peekerIdSource++;
261 m_peekerToNode.insert(peekerId, nullptr);
262 return peekerId;
263}
264
265bool QXcbEventQueue::removePeekerId(qint32 peekerId)
266{
267 const auto it = m_peekerToNode.constFind(peekerId);
268 if (it == m_peekerToNode.constEnd()) {
269 qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
270 return false;
271 }
272 m_peekerToNode.erase(it);
273 if (m_peekerToNode.isEmpty()) {
274 m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
275 m_peekerIndexCacheDirty = false;
276 }
277 return true;
278}
279
280bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
281 PeekOptions option, qint32 peekerId)
282{
283 const bool peekerIdProvided = peekerId != -1;
284 auto peekerToNodeIt = m_peekerToNode.find(peekerId);
285
286 if (peekerIdProvided && peekerToNodeIt == m_peekerToNode.end()) {
287 qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
288 return false;
289 }
290
291 const bool useCache = option.testFlag(PeekOption::PeekFromCachedIndex);
292 if (useCache && !peekerIdProvided) {
293 qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
294 return false;
295 }
296
297 if (peekerIdProvided && m_peekerIndexCacheDirty) {
298 for (auto &node : m_peekerToNode) // reset cache
299 node = nullptr;
300 m_peekerIndexCacheDirty = false;
301 }
302
303 flushBufferedEvents();
304 if (isEmpty())
305 return false;
306
307 const auto startNode = [this, useCache, peekerToNodeIt]() -> QXcbEventNode * {
308 if (useCache) {
309 const QXcbEventNode *cachedNode = peekerToNodeIt.value();
310 if (!cachedNode)
311 return m_head; // cache was reset
312 if (cachedNode == m_flushedTail)
313 return nullptr; // no new events since the last call
314 return cachedNode->next;
315 }
316 return m_head;
317 }();
318
319 if (!startNode)
320 return false;
321
322 // A peeker may call QCoreApplication::processEvents(), which will cause
323 // QXcbConnection::processXcbEvents() to modify the queue we are currently
324 // looping through;
325 m_queueModified = false;
326 bool result = false;
327
328 QXcbEventNode *node = startNode;
329 do {
330 xcb_generic_event_t *event = node->event;
331 if (event && peeker(event, peekerData)) {
332 result = true;
333 break;
334 }
335 if (node == m_flushedTail)
336 break;
337 node = node->next;
338 } while (!m_queueModified);
339
340 // Update the cached index if the queue was not modified, and hence the
341 // cache is still valid.
342 if (peekerIdProvided && node != startNode && !m_queueModified) {
343 // Before updating, make sure that a peeker callback did not remove
344 // the peeker id.
345 peekerToNodeIt = m_peekerToNode.find(peekerId);
346 if (peekerToNodeIt != m_peekerToNode.end())
347 *peekerToNodeIt = node; // id still in the cache, update node
348 }
349
350 return result;
351}
352
353void QXcbEventQueue::waitForNewEvents(unsigned long time)
354{
355 QMutexLocker locker(&m_newEventsMutex);
356 QXcbEventNode *tailBeforeFlush = m_flushedTail;
357 flushBufferedEvents();
358 if (tailBeforeFlush != m_flushedTail)
359 return;
360 m_newEventsCondition.wait(&m_newEventsMutex, time);
361}
362
363void QXcbEventQueue::sendCloseConnectionEvent() const
364{
365 // A hack to close XCB connection. Apparently XCB does not have any APIs for this?
366 xcb_client_message_event_t event;
367 memset(&event, 0, sizeof(event));
368
369 xcb_connection_t *c = m_connection->xcb_connection();
370 const xcb_window_t window = xcb_generate_id(c);
371 xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_connection->setup());
372 xcb_screen_t *screen = it.data;
373 xcb_create_window(c, XCB_COPY_FROM_PARENT,
374 window, screen->root,
375 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
376 screen->root_visual, 0, nullptr);
377
378 event.response_type = XCB_CLIENT_MESSAGE;
379 event.format = 32;
380 event.sequence = 0;
381 event.window = window;
382 event.type = m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION);
383 event.data.data32[0] = 0;
384
385 xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
386 xcb_destroy_window(c, window);
387 xcb_flush(c);
388}
389
390bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
391{
392 if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
393 auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
394 if (clientMessage->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
395 m_closeConnectionDetected = true;
396 }
397 return m_closeConnectionDetected;
398}
399
400QT_END_NAMESPACE
401