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(event: nullptr);
96 m_tail.store(p: m_head, m: 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(flags: QEventLoop::AllEvents))
110 free(ptr: 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(flag: 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(i: 1, m: std::memory_order_release);
179}
180
181void QXcbEventQueue::flushBufferedEvents()
182{
183 m_flushedTail = m_tail.load(m: 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(i: 0, m: 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(p: tail, m: std::memory_order_release);
223 } else {
224 free(ptr: event);
225 }
226 };
227
228 while (!m_closeConnectionDetected && (event = xcb_wait_for_event(c: connection))) {
229 // This lock can block only if there are users of waitForNewEvents().
230 // Currently only the clipboard implementation relies on it.
231 m_newEventsMutex.lock();
232 enqueueEvent(event);
233 while (!m_closeConnectionDetected && (event = xcb_poll_for_queued_event(c: connection)))
234 enqueueEvent(event);
235
236 m_newEventsCondition.wakeOne();
237 m_newEventsMutex.unlock();
238 wakeUpDispatcher();
239 }
240
241 if (!m_closeConnectionDetected) {
242 // Connection was terminated not by us. Wake up dispatcher, which will
243 // call processXcbEvents(), where we handle the connection errors via
244 // xcb_connection_has_error().
245 wakeUpDispatcher();
246 }
247}
248
249void QXcbEventQueue::wakeUpDispatcher()
250{
251 QMutexLocker locker(&qAppExiting);
252 if (!dispatcherOwnerDestructing) {
253 // This thread can run before a dispatcher has been created,
254 // so check if it is ready.
255 if (QCoreApplication::eventDispatcher())
256 QCoreApplication::eventDispatcher()->wakeUp();
257 }
258}
259
260qint32 QXcbEventQueue::generatePeekerId()
261{
262 const qint32 peekerId = m_peekerIdSource++;
263 m_peekerToNode.insert(akey: peekerId, avalue: nullptr);
264 return peekerId;
265}
266
267bool QXcbEventQueue::removePeekerId(qint32 peekerId)
268{
269 const auto it = m_peekerToNode.constFind(akey: peekerId);
270 if (it == m_peekerToNode.constEnd()) {
271 qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
272 return false;
273 }
274 m_peekerToNode.erase(it);
275 if (m_peekerToNode.isEmpty()) {
276 m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
277 m_peekerIndexCacheDirty = false;
278 }
279 return true;
280}
281
282bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
283 PeekOptions option, qint32 peekerId)
284{
285 const bool peekerIdProvided = peekerId != -1;
286 auto peekerToNodeIt = m_peekerToNode.find(akey: peekerId);
287
288 if (peekerIdProvided && peekerToNodeIt == m_peekerToNode.end()) {
289 qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
290 return false;
291 }
292
293 const bool useCache = option.testFlag(flag: PeekOption::PeekFromCachedIndex);
294 if (useCache && !peekerIdProvided) {
295 qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
296 return false;
297 }
298
299 if (peekerIdProvided && m_peekerIndexCacheDirty) {
300 for (auto &node : m_peekerToNode) // reset cache
301 node = nullptr;
302 m_peekerIndexCacheDirty = false;
303 }
304
305 flushBufferedEvents();
306 if (isEmpty())
307 return false;
308
309 const auto startNode = [this, useCache, peekerToNodeIt]() -> QXcbEventNode * {
310 if (useCache) {
311 const QXcbEventNode *cachedNode = peekerToNodeIt.value();
312 if (!cachedNode)
313 return m_head; // cache was reset
314 if (cachedNode == m_flushedTail)
315 return nullptr; // no new events since the last call
316 return cachedNode->next;
317 }
318 return m_head;
319 }();
320
321 if (!startNode)
322 return false;
323
324 // A peeker may call QCoreApplication::processEvents(), which will cause
325 // QXcbConnection::processXcbEvents() to modify the queue we are currently
326 // looping through;
327 m_queueModified = false;
328 bool result = false;
329
330 QXcbEventNode *node = startNode;
331 do {
332 xcb_generic_event_t *event = node->event;
333 if (event && peeker(event, peekerData)) {
334 result = true;
335 break;
336 }
337 if (node == m_flushedTail)
338 break;
339 node = node->next;
340 } while (!m_queueModified);
341
342 // Update the cached index if the queue was not modified, and hence the
343 // cache is still valid.
344 if (peekerIdProvided && node != startNode && !m_queueModified) {
345 // Before updating, make sure that a peeker callback did not remove
346 // the peeker id.
347 peekerToNodeIt = m_peekerToNode.find(akey: peekerId);
348 if (peekerToNodeIt != m_peekerToNode.end())
349 *peekerToNodeIt = node; // id still in the cache, update node
350 }
351
352 return result;
353}
354
355void QXcbEventQueue::waitForNewEvents(const QXcbEventNode *sinceFlushedTail,
356 unsigned long time)
357{
358 QMutexLocker locker(&m_newEventsMutex);
359 flushBufferedEvents();
360 if (sinceFlushedTail != m_flushedTail)
361 return;
362 m_newEventsCondition.wait(lockedMutex: &m_newEventsMutex, time);
363}
364
365void QXcbEventQueue::sendCloseConnectionEvent() const
366{
367 // A hack to close XCB connection. Apparently XCB does not have any APIs for this?
368 xcb_client_message_event_t event;
369 memset(s: &event, c: 0, n: sizeof(event));
370
371 xcb_connection_t *c = m_connection->xcb_connection();
372 const xcb_window_t window = xcb_generate_id(c);
373 xcb_screen_iterator_t it = xcb_setup_roots_iterator(R: m_connection->setup());
374 xcb_screen_t *screen = it.data;
375 xcb_create_window(c, XCB_COPY_FROM_PARENT,
376 wid: window, parent: screen->root,
377 x: 0, y: 0, width: 1, height: 1, border_width: 0, class: XCB_WINDOW_CLASS_INPUT_ONLY,
378 visual: screen->root_visual, value_mask: 0, value_list: nullptr);
379
380 event.response_type = XCB_CLIENT_MESSAGE;
381 event.format = 32;
382 event.sequence = 0;
383 event.window = window;
384 event.type = m_connection->atom(qatom: QXcbAtom::_QT_CLOSE_CONNECTION);
385 event.data.data32[0] = 0;
386
387 xcb_send_event(c, propagate: false, destination: window, event_mask: XCB_EVENT_MASK_NO_EVENT, event: reinterpret_cast<const char *>(&event));
388 xcb_destroy_window(c, window);
389 xcb_flush(c);
390}
391
392bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
393{
394 if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
395 auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
396 if (clientMessage->type == m_connection->atom(qatom: QXcbAtom::_QT_CLOSE_CONNECTION))
397 m_closeConnectionDetected = true;
398 }
399 return m_closeConnectionDetected;
400}
401
402QT_END_NAMESPACE
403

source code of qtbase/src/plugins/platforms/xcb/qxcbeventqueue.cpp