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 
48 QT_BEGIN_NAMESPACE
49 
50 static QBasicMutex qAppExiting;
51 static 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 
QXcbEventQueue(QXcbConnection * connection)81 QXcbEventQueue::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 
~QXcbEventQueue()101 QXcbEventQueue::~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 
takeFirst(QEventLoop::ProcessEventsFlags flags)118 xcb_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 
takeFirst()147 xcb_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 
dequeueNode()171 void 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 
flushBufferedEvents()181 void QXcbEventQueue::flushBufferedEvents()
182 {
183     m_flushedTail = m_tail.load(std::memory_order_acquire);
184 }
185 
qXcbEventNodeFactory(xcb_generic_event_t * event)186 QXcbEventNode *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 
run()212 void 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         // 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(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 
wakeUpDispatcher()249 void 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 
generatePeekerId()260 qint32 QXcbEventQueue::generatePeekerId()
261 {
262     const qint32 peekerId = m_peekerIdSource++;
263     m_peekerToNode.insert(peekerId, nullptr);
264     return peekerId;
265 }
266 
removePeekerId(qint32 peekerId)267 bool QXcbEventQueue::removePeekerId(qint32 peekerId)
268 {
269     const auto it = m_peekerToNode.constFind(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 
peekEventQueue(PeekerCallback peeker,void * peekerData,PeekOptions option,qint32 peekerId)282 bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
283                                     PeekOptions option, qint32 peekerId)
284 {
285     const bool peekerIdProvided = peekerId != -1;
286     auto peekerToNodeIt = m_peekerToNode.find(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(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(peekerId);
348         if (peekerToNodeIt != m_peekerToNode.end())
349             *peekerToNodeIt = node; // id still in the cache, update node
350     }
351 
352     return result;
353 }
354 
waitForNewEvents(const QXcbEventNode * sinceFlushedTail,unsigned long time)355 void 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(&m_newEventsMutex, time);
363 }
364 
sendCloseConnectionEvent() const365 void 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(&event, 0, 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(m_connection->setup());
374     xcb_screen_t *screen = it.data;
375     xcb_create_window(c, XCB_COPY_FROM_PARENT,
376                       window, screen->root,
377                       0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
378                       screen->root_visual, 0, 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(QXcbAtom::_QT_CLOSE_CONNECTION);
385     event.data.data32[0] = 0;
386 
387     xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
388     xcb_destroy_window(c, window);
389     xcb_flush(c);
390 }
391 
isCloseConnectionEvent(const xcb_generic_event_t * event)392 bool 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(QXcbAtom::_QT_CLOSE_CONNECTION))
397             m_closeConnectionDetected = true;
398     }
399     return m_closeConnectionDetected;
400 }
401 
402 QT_END_NAMESPACE
403