xcb: lock-free event processing
For details how this works refer to the documentation in the patch. The follow-up patches will switch to calling processXcbEvents() on every event loop iteration. With the existing code that would mean frequent locking of shared data (event queue). Acquiring a lock is fast, but lock contention isn't. To avoid potential problems, reimplement xcb event processing to be lock-free. Besides theoretical performance benefits, this definitally improves code readability in qxcbconnection.cpp. Thanks to Mikhail Svetkin for questioning the design of the existing code. Done-with: Mikhail Svetkin <mikhail.svetkin@qt.io> Change-Id: I935f2b6ca802580f5c80205aef7b2f9afc172d26 Reviewed-by: Mikhail Svetkin <mikhail.svetkin@qt.io> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>bb10
parent
a880780ff9
commit
243c3044b6
|
|
@ -807,18 +807,18 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i
|
|||
{
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
QXcbEventQueue *queue = connection()->eventQueue();
|
||||
do {
|
||||
auto e = connection()->checkEvent([window, type](xcb_generic_event_t *event, int eventType) {
|
||||
auto e = queue->peek([window, type](xcb_generic_event_t *event, int eventType) {
|
||||
if (eventType != type)
|
||||
return false;
|
||||
if (eventType == XCB_PROPERTY_NOTIFY) {
|
||||
auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
|
||||
if (propertyNotify->window == window)
|
||||
return true;
|
||||
} else if (eventType == XCB_SELECTION_NOTIFY) {
|
||||
return propertyNotify->window == window;
|
||||
}
|
||||
if (eventType == XCB_SELECTION_NOTIFY) {
|
||||
auto selectionNotify = reinterpret_cast<xcb_selection_notify_event_t *>(event);
|
||||
if (selectionNotify->requestor == window)
|
||||
return true;
|
||||
return selectionNotify->requestor == window;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
@ -833,7 +833,7 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i
|
|||
|
||||
// process other clipboard events, since someone is probably requesting data from us
|
||||
auto clipboardAtom = atom(QXcbAtom::CLIPBOARD);
|
||||
e = connection()->checkEvent([clipboardAtom](xcb_generic_event_t *event, int type) {
|
||||
e = queue->peek([clipboardAtom](xcb_generic_event_t *event, int type) {
|
||||
xcb_atom_t selection = XCB_ATOM_NONE;
|
||||
if (type == XCB_SELECTION_REQUEST)
|
||||
selection = reinterpret_cast<xcb_selection_request_event_t *>(event)->selection;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
#include "qxcbglintegration.h"
|
||||
#include "qxcbcursor.h"
|
||||
#include "qxcbbackingstore.h"
|
||||
#include "qxcbeventqueue.h"
|
||||
|
||||
#include <QSocketNotifier>
|
||||
#include <QAbstractEventDispatcher>
|
||||
|
|
@ -97,6 +98,7 @@ Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices")
|
|||
Q_LOGGING_CATEGORY(lcQpaXInputEvents, "qt.qpa.input.events")
|
||||
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
|
||||
Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events")
|
||||
Q_LOGGING_CATEGORY(lcQpaEventReader, "qt.qpa.events.reader")
|
||||
Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb") // for general (uncategorized) XCB logging
|
||||
Q_LOGGING_CATEGORY(lcQpaPeeker, "qt.qpa.peeker")
|
||||
Q_LOGGING_CATEGORY(lcQpaKeyboard, "qt.qpa.xkeyboard")
|
||||
|
|
@ -550,8 +552,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
|
|||
return;
|
||||
}
|
||||
|
||||
m_reader = new QXcbEventReader(this);
|
||||
m_reader->start();
|
||||
m_eventQueue = new QXcbEventQueue(this);
|
||||
|
||||
xcb_extension_t *extensions[] = {
|
||||
&xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id,
|
||||
|
|
@ -618,12 +619,8 @@ QXcbConnection::~QXcbConnection()
|
|||
#if QT_CONFIG(draganddrop)
|
||||
delete m_drag;
|
||||
#endif
|
||||
if (m_reader && m_reader->isRunning()) {
|
||||
sendConnectionEvent(QXcbAtom::_QT_CLOSE_CONNECTION);
|
||||
m_reader->wait();
|
||||
}
|
||||
|
||||
delete m_reader;
|
||||
if (m_eventQueue)
|
||||
delete m_eventQueue;
|
||||
|
||||
QXcbIntegration *integration = QXcbIntegration::instance();
|
||||
// Delete screens in reverse order to avoid crash in case of multiple screens
|
||||
|
|
@ -1226,151 +1223,6 @@ void QXcbConnection::addPeekFunc(PeekFunc f)
|
|||
m_peekFuncs.append(f);
|
||||
}
|
||||
|
||||
qint32 QXcbConnection::generatePeekerId()
|
||||
{
|
||||
qint32 peekerId = m_peekerIdSource++;
|
||||
m_peekerToCachedIndex.insert(peekerId, 0);
|
||||
return peekerId;
|
||||
}
|
||||
|
||||
bool QXcbConnection::removePeekerId(qint32 peekerId)
|
||||
{
|
||||
if (!m_peekerToCachedIndex.contains(peekerId)) {
|
||||
qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
|
||||
return false;
|
||||
}
|
||||
m_peekerToCachedIndex.remove(peekerId);
|
||||
if (m_peekerToCachedIndex.isEmpty()) {
|
||||
m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
|
||||
m_peekerIndexCacheDirty = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QXcbConnection::peekEventQueue(PeekerCallback peeker, void *peekerData,
|
||||
PeekOptions option, qint32 peekerId)
|
||||
{
|
||||
bool peekerIdProvided = peekerId != -1;
|
||||
if (peekerIdProvided && !m_peekerToCachedIndex.contains(peekerId)) {
|
||||
qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool peekFromCachedIndex = option.testFlag(PeekOption::PeekFromCachedIndex);
|
||||
if (peekFromCachedIndex && !peekerIdProvided) {
|
||||
qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (peekerIdProvided && m_peekerIndexCacheDirty) {
|
||||
// When the main event loop has flushed the buffered XCB events into the window
|
||||
// system event queue, the cached indices are not valid anymore and need reset.
|
||||
auto it = m_peekerToCachedIndex.begin();
|
||||
while (it != m_peekerToCachedIndex.constEnd()) {
|
||||
(*it) = 0;
|
||||
++it;
|
||||
}
|
||||
m_peekerIndexCacheDirty = false;
|
||||
}
|
||||
|
||||
qint32 peekerIndex = peekFromCachedIndex ? m_peekerToCachedIndex.value(peekerId) : 0;
|
||||
qint32 startingIndex = peekerIndex;
|
||||
bool result = false;
|
||||
m_mainEventLoopFlushedQueue = false;
|
||||
|
||||
QXcbEventArray *eventqueue = m_reader->lock();
|
||||
|
||||
if (Q_UNLIKELY(lcQpaPeeker().isDebugEnabled())) {
|
||||
qCDebug(lcQpaPeeker, "[%d] peeker index: %d | mode: %s | queue size: %d", peekerId,
|
||||
peekerIndex, peekFromCachedIndex ? "cache" : "start", eventqueue->size());
|
||||
}
|
||||
while (peekerIndex < eventqueue->size() && !result && !m_mainEventLoopFlushedQueue) {
|
||||
xcb_generic_event_t *event = eventqueue->at(peekerIndex++);
|
||||
if (!event)
|
||||
continue;
|
||||
if (Q_UNLIKELY(lcQpaPeeker().isDebugEnabled())) {
|
||||
QString debug = QString((QLatin1String("[%1] peeking at index: %2")))
|
||||
.arg(peekerId).arg(peekerIndex - 1);
|
||||
printXcbEvent(lcQpaPeeker(), debug.toLatin1(), event);
|
||||
}
|
||||
// A peeker may call QCoreApplication::processEvents(), which has two implications:
|
||||
// 1) We need to make the lock available for QXcbConnection::processXcbEvents(),
|
||||
// otherwise we will deadlock;
|
||||
// 2) QXcbConnection::processXcbEvents() will flush the queue we are currently
|
||||
// looping through;
|
||||
m_reader->unlock();
|
||||
result = peeker(event, peekerData);
|
||||
m_reader->lock();
|
||||
}
|
||||
|
||||
m_reader->unlock();
|
||||
|
||||
if (peekerIdProvided && peekerIndex != startingIndex && !m_mainEventLoopFlushedQueue) {
|
||||
auto it = m_peekerToCachedIndex.find(peekerId);
|
||||
// Make sure that a peeker callback did not remove the peeker id
|
||||
if (it != m_peekerToCachedIndex.constEnd())
|
||||
(*it) = peekerIndex;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QXcbEventReader::QXcbEventReader(QXcbConnection *connection)
|
||||
: m_connection(connection)
|
||||
{
|
||||
}
|
||||
|
||||
void QXcbEventReader::start()
|
||||
{
|
||||
connect(this, &QXcbEventReader::eventPending, m_connection, &QXcbConnection::processXcbEvents, Qt::QueuedConnection);
|
||||
connect(this, &QXcbEventReader::finished, m_connection, &QXcbConnection::processXcbEvents);
|
||||
QThread::start();
|
||||
}
|
||||
|
||||
void QXcbEventReader::registerEventDispatcher(QAbstractEventDispatcher *dispatcher)
|
||||
{
|
||||
// Flush the xcb connection before the event dispatcher is going to block.
|
||||
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_connection, &QXcbConnection::flush);
|
||||
}
|
||||
|
||||
void QXcbEventReader::run()
|
||||
{
|
||||
xcb_generic_event_t *event;
|
||||
while (m_connection && (event = xcb_wait_for_event(m_connection->xcb_connection()))) {
|
||||
m_mutex.lock();
|
||||
addEvent(event);
|
||||
while (m_connection && (event = xcb_poll_for_queued_event(m_connection->xcb_connection())))
|
||||
addEvent(event);
|
||||
m_mutex.unlock();
|
||||
emit eventPending();
|
||||
}
|
||||
|
||||
m_mutex.lock();
|
||||
for (int i = 0; i < m_events.size(); ++i)
|
||||
free(m_events.at(i));
|
||||
m_events.clear();
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void QXcbEventReader::addEvent(xcb_generic_event_t *event)
|
||||
{
|
||||
if ((event->response_type & ~0x80) == XCB_CLIENT_MESSAGE
|
||||
&& (reinterpret_cast<xcb_client_message_event_t *>(event))->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
|
||||
m_connection = 0;
|
||||
m_events << event;
|
||||
}
|
||||
|
||||
QXcbEventArray *QXcbEventReader::lock()
|
||||
{
|
||||
m_mutex.lock();
|
||||
return &m_events;
|
||||
}
|
||||
|
||||
void QXcbEventReader::unlock()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void QXcbConnection::setFocusWindow(QWindow *w)
|
||||
{
|
||||
m_focusWindow = w ? static_cast<QXcbWindow *>(w->handle()) : nullptr;
|
||||
|
|
@ -1397,31 +1249,6 @@ void QXcbConnection::ungrabServer()
|
|||
xcb_ungrab_server(m_connection);
|
||||
}
|
||||
|
||||
void QXcbConnection::sendConnectionEvent(QXcbAtom::Atom a, uint id)
|
||||
{
|
||||
xcb_client_message_event_t event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
const xcb_window_t eventListener = xcb_generate_id(m_connection);
|
||||
xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup);
|
||||
xcb_screen_t *screen = it.data;
|
||||
xcb_create_window(m_connection, XCB_COPY_FROM_PARENT,
|
||||
eventListener, screen->root,
|
||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
|
||||
screen->root_visual, 0, 0);
|
||||
|
||||
event.response_type = XCB_CLIENT_MESSAGE;
|
||||
event.format = 32;
|
||||
event.sequence = 0;
|
||||
event.window = eventListener;
|
||||
event.type = atom(a);
|
||||
event.data.data32[0] = id;
|
||||
|
||||
xcb_send_event(xcb_connection(), false, eventListener, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
|
||||
xcb_destroy_window(m_connection, eventListener);
|
||||
xcb_flush(xcb_connection());
|
||||
}
|
||||
|
||||
xcb_timestamp_t QXcbConnection::getTimestamp()
|
||||
{
|
||||
// send a dummy event to myself to get the timestamp from X server.
|
||||
|
|
@ -1433,12 +1260,10 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
|
|||
connection()->flush();
|
||||
|
||||
xcb_generic_event_t *event = nullptr;
|
||||
// lets keep this inside a loop to avoid a possible race condition, where
|
||||
// reader thread has not yet had the time to acquire the mutex in order
|
||||
// to add the new set of events to its event queue
|
||||
|
||||
while (!event) {
|
||||
connection()->sync();
|
||||
event = checkEvent([window, dummyAtom](xcb_generic_event_t *event, int type) {
|
||||
event = eventQueue()->peek([window, dummyAtom](xcb_generic_event_t *event, int type) {
|
||||
if (type != XCB_PROPERTY_NOTIFY)
|
||||
return false;
|
||||
auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
|
||||
|
|
@ -1546,10 +1371,6 @@ static inline bool isXIType(xcb_generic_event_t *event, int opCode, uint16_t typ
|
|||
return e->event_type == type;
|
||||
}
|
||||
#endif
|
||||
static inline bool isValid(xcb_generic_event_t *event)
|
||||
{
|
||||
return event && (event->response_type & ~0x80);
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
|
||||
|
|
@ -1563,22 +1384,18 @@ static inline bool isValid(xcb_generic_event_t *event)
|
|||
3) Or add public API to Qt for disabling event compression QTBUG-44964
|
||||
|
||||
*/
|
||||
bool QXcbConnection::compressEvent(xcb_generic_event_t *event, int currentIndex, QXcbEventArray *eventqueue) const
|
||||
bool QXcbConnection::compressEvent(xcb_generic_event_t *event) const
|
||||
{
|
||||
uint responseType = event->response_type & ~0x80;
|
||||
int nextIndex = currentIndex + 1;
|
||||
|
||||
if (responseType == XCB_MOTION_NOTIFY) {
|
||||
// compress XCB_MOTION_NOTIFY notify events
|
||||
for (int j = nextIndex; j < eventqueue->size(); ++j) {
|
||||
xcb_generic_event_t *next = eventqueue->at(j);
|
||||
if (!isValid(next))
|
||||
continue;
|
||||
if (next->response_type == XCB_MOTION_NOTIFY)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
|
||||
[](xcb_generic_event_t *, int type) {
|
||||
return type == XCB_MOTION_NOTIFY;
|
||||
});
|
||||
}
|
||||
|
||||
#if QT_CONFIG(xcb_xinput)
|
||||
// compress XI_* events
|
||||
if (responseType == XCB_GE_GENERIC) {
|
||||
|
|
@ -1588,50 +1405,44 @@ bool QXcbConnection::compressEvent(xcb_generic_event_t *event, int currentIndex,
|
|||
// compress XI_Motion
|
||||
if (isXIType(event, m_xiOpCode, XCB_INPUT_MOTION)) {
|
||||
#if QT_CONFIG(tabletevent)
|
||||
auto *xdev = reinterpret_cast<xcb_input_motion_event_t *>(event);
|
||||
auto xdev = reinterpret_cast<xcb_input_motion_event_t *>(event);
|
||||
if (!QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents) &&
|
||||
const_cast<QXcbConnection *>(this)->tabletDataForDevice(xdev->sourceid))
|
||||
return false;
|
||||
#endif // QT_CONFIG(tabletevent)
|
||||
for (int j = nextIndex; j < eventqueue->size(); ++j) {
|
||||
xcb_generic_event_t *next = eventqueue->at(j);
|
||||
if (!isValid(next))
|
||||
continue;
|
||||
if (isXIType(next, m_xiOpCode, XCB_INPUT_MOTION))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
|
||||
[this](xcb_generic_event_t *next, int) {
|
||||
return isXIType(next, m_xiOpCode, XCB_INPUT_MOTION);
|
||||
});
|
||||
}
|
||||
|
||||
// compress XI_TouchUpdate for the same touch point id
|
||||
if (isXIType(event, m_xiOpCode, XCB_INPUT_TOUCH_UPDATE)) {
|
||||
auto *touchUpdateEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(event);
|
||||
auto touchUpdateEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(event);
|
||||
uint32_t id = touchUpdateEvent->detail % INT_MAX;
|
||||
for (int j = nextIndex; j < eventqueue->size(); ++j) {
|
||||
xcb_generic_event_t *next = eventqueue->at(j);
|
||||
if (!isValid(next))
|
||||
continue;
|
||||
if (isXIType(next, m_xiOpCode, XCB_INPUT_TOUCH_UPDATE)) {
|
||||
auto *touchUpdateNextEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(next);
|
||||
if (id == touchUpdateNextEvent->detail % INT_MAX)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
|
||||
[this, &id](xcb_generic_event_t *next, int) {
|
||||
if (!isXIType(next, m_xiOpCode, XCB_INPUT_TOUCH_UPDATE))
|
||||
return false;
|
||||
auto touchUpdateNextEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(next);
|
||||
return id == touchUpdateNextEvent->detail % INT_MAX;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (responseType == XCB_CONFIGURE_NOTIFY) {
|
||||
// compress multiple configure notify events for the same window
|
||||
for (int j = nextIndex; j < eventqueue->size(); ++j) {
|
||||
xcb_generic_event_t *next = eventqueue->at(j);
|
||||
if (isValid(next) && next->response_type == XCB_CONFIGURE_NOTIFY
|
||||
&& reinterpret_cast<xcb_configure_notify_event_t *>(next)->event == reinterpret_cast<xcb_configure_notify_event_t *>(event)->event)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
|
||||
[event](xcb_generic_event_t *next, int type) {
|
||||
if (type != XCB_CONFIGURE_NOTIFY)
|
||||
return false;
|
||||
auto currentEvent = reinterpret_cast<xcb_configure_notify_event_t *>(event);
|
||||
auto nextEvent = reinterpret_cast<xcb_configure_notify_event_t *>(next);
|
||||
return currentEvent->event == nextEvent->event;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -1645,22 +1456,17 @@ void QXcbConnection::processXcbEvents()
|
|||
exit(1);
|
||||
}
|
||||
|
||||
QXcbEventArray *eventqueue = m_reader->lock();
|
||||
m_eventQueue->flushBufferedEvents();
|
||||
|
||||
for (int i = 0; i < eventqueue->size(); ++i) {
|
||||
xcb_generic_event_t *event = eventqueue->at(i);
|
||||
if (!event)
|
||||
continue;
|
||||
while (xcb_generic_event_t *event = m_eventQueue->takeFirst()) {
|
||||
QScopedPointer<xcb_generic_event_t, QScopedPointerPodDeleter> eventGuard(event);
|
||||
(*eventqueue)[i] = 0;
|
||||
|
||||
if (!(event->response_type & ~0x80)) {
|
||||
handleXcbError(reinterpret_cast<xcb_generic_error_t *>(event));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) &&
|
||||
compressEvent(event, i, eventqueue))
|
||||
compressEvent(event))
|
||||
continue;
|
||||
|
||||
#ifndef QT_NO_CLIPBOARD
|
||||
|
|
@ -1679,21 +1485,19 @@ void QXcbConnection::processXcbEvents()
|
|||
m_peekFuncs.erase(std::remove_if(m_peekFuncs.begin(), m_peekFuncs.end(),
|
||||
isWaitingFor),
|
||||
m_peekFuncs.end());
|
||||
m_reader->unlock();
|
||||
|
||||
handleXcbEvent(event);
|
||||
m_reader->lock();
|
||||
|
||||
// The lock-based solution used to free the lock inside this loop,
|
||||
// hence allowing for more events to arrive. ### Check if we want
|
||||
// this flush here after QTBUG-70095
|
||||
m_eventQueue->flushBufferedEvents();
|
||||
}
|
||||
|
||||
eventqueue->clear();
|
||||
|
||||
m_reader->unlock();
|
||||
|
||||
m_peekerIndexCacheDirty = m_mainEventLoopFlushedQueue = true;
|
||||
|
||||
// Indicate with a null event that the event the callbacks are waiting for
|
||||
// is not in the queue currently.
|
||||
for (PeekFunc f : qAsConst(m_peekFuncs))
|
||||
f(this, 0);
|
||||
f(this, nullptr);
|
||||
m_peekFuncs.clear();
|
||||
|
||||
xcb_flush(xcb_connection());
|
||||
|
|
|
|||
|
|
@ -47,15 +47,14 @@
|
|||
#include "qxcbexport.h"
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
#include <QVarLengthArray>
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/private/qglobal_p.h>
|
||||
|
||||
#include "qxcbeventqueue.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
|
|
@ -84,6 +83,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaXcb)
|
|||
Q_DECLARE_LOGGING_CATEGORY(lcQpaPeeker)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaKeyboard)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaXDnd)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaEventReader)
|
||||
|
||||
class QXcbVirtualDesktop;
|
||||
class QXcbScreen;
|
||||
|
|
@ -305,35 +305,6 @@ namespace QXcbAtom {
|
|||
};
|
||||
}
|
||||
|
||||
typedef QVarLengthArray<xcb_generic_event_t *, 64> QXcbEventArray;
|
||||
|
||||
class QXcbConnection;
|
||||
class QXcbEventReader : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QXcbEventReader(QXcbConnection *connection);
|
||||
|
||||
void run() override;
|
||||
|
||||
QXcbEventArray *lock();
|
||||
void unlock();
|
||||
|
||||
void start();
|
||||
|
||||
void registerEventDispatcher(QAbstractEventDispatcher *dispatcher);
|
||||
|
||||
signals:
|
||||
void eventPending();
|
||||
|
||||
private:
|
||||
void addEvent(xcb_generic_event_t *event);
|
||||
|
||||
QMutex m_mutex;
|
||||
QXcbEventArray m_events;
|
||||
QXcbConnection *m_connection;
|
||||
};
|
||||
|
||||
class QXcbWindowEventListener
|
||||
{
|
||||
public:
|
||||
|
|
@ -375,7 +346,6 @@ private:
|
|||
QXcbWindow *m_window;
|
||||
};
|
||||
|
||||
class QAbstractEventDispatcher;
|
||||
class Q_XCB_EXPORT QXcbConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
@ -385,6 +355,7 @@ public:
|
|||
|
||||
QXcbConnection *connection() const { return const_cast<QXcbConnection *>(this); }
|
||||
bool isConnected() const;
|
||||
QXcbEventQueue *eventQueue() const { return m_eventQueue; }
|
||||
|
||||
const QList<QXcbVirtualDesktop *> &virtualDesktops() const { return m_virtualDesktops; }
|
||||
const QList<QXcbScreen *> &screens() const { return m_screens; }
|
||||
|
|
@ -445,21 +416,9 @@ public:
|
|||
QXcbWindowEventListener *windowEventListenerFromId(xcb_window_t id);
|
||||
QXcbWindow *platformWindowFromId(xcb_window_t id);
|
||||
|
||||
template<typename Functor>
|
||||
inline xcb_generic_event_t *checkEvent(Functor &&filter, bool removeFromQueue = true);
|
||||
|
||||
typedef bool (*PeekFunc)(QXcbConnection *, xcb_generic_event_t *);
|
||||
void addPeekFunc(PeekFunc f);
|
||||
|
||||
// Peek at all queued events
|
||||
qint32 generatePeekerId();
|
||||
bool removePeekerId(qint32 peekerId);
|
||||
enum PeekOption { PeekDefault = 0, PeekFromCachedIndex = 1 }; // see qx11info_x11.h
|
||||
Q_DECLARE_FLAGS(PeekOptions, PeekOption)
|
||||
typedef bool (*PeekerCallback)(xcb_generic_event_t *event, void *peekerData);
|
||||
bool peekEventQueue(PeekerCallback peeker, void *peekerData = nullptr,
|
||||
PeekOptions option = PeekDefault, qint32 peekerId = -1);
|
||||
|
||||
inline xcb_timestamp_t time() const { return m_time; }
|
||||
inline void setTime(xcb_timestamp_t t) { if (t > m_time) m_time = t; }
|
||||
|
||||
|
|
@ -530,24 +489,20 @@ public:
|
|||
void abortSystemMoveResizeForTouch();
|
||||
bool isTouchScreen(int id);
|
||||
#endif
|
||||
QXcbEventReader *eventReader() const { return m_reader; }
|
||||
|
||||
bool canGrab() const { return m_canGrabServer; }
|
||||
|
||||
QXcbGlIntegration *glIntegration() const;
|
||||
|
||||
void flush() { xcb_flush(m_connection); }
|
||||
|
||||
protected:
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
public slots:
|
||||
void flush() { xcb_flush(m_connection); }
|
||||
|
||||
private slots:
|
||||
void processXcbEvents();
|
||||
|
||||
private:
|
||||
void initializeAllAtoms();
|
||||
void sendConnectionEvent(QXcbAtom::Atom atom, uint id = 0);
|
||||
void initializeShm();
|
||||
void initializeXFixes();
|
||||
void initializeXRender();
|
||||
|
|
@ -567,7 +522,7 @@ private:
|
|||
xcb_randr_get_output_info_reply_t *outputInfo);
|
||||
void destroyScreen(QXcbScreen *screen);
|
||||
void initializeScreens();
|
||||
bool compressEvent(xcb_generic_event_t *event, int currentIndex, QXcbEventArray *eventqueue) const;
|
||||
bool compressEvent(xcb_generic_event_t *event) const;
|
||||
|
||||
bool m_xi2Enabled = false;
|
||||
#if QT_CONFIG(xcb_xinput)
|
||||
|
|
@ -670,7 +625,7 @@ private:
|
|||
#if QT_CONFIG(xcb_xlib)
|
||||
void *m_xlib_display = nullptr;
|
||||
#endif
|
||||
QXcbEventReader *m_reader = nullptr;
|
||||
QXcbEventQueue *m_eventQueue = nullptr;
|
||||
|
||||
#if QT_CONFIG(xcb_xinput)
|
||||
QHash<int, TouchDeviceData> m_touchDevices;
|
||||
|
|
@ -722,11 +677,7 @@ private:
|
|||
|
||||
xcb_window_t m_qtSelectionOwner = 0;
|
||||
|
||||
bool m_mainEventLoopFlushedQueue = false;
|
||||
qint32 m_peekerIdSource = 0;
|
||||
bool m_peekerIndexCacheDirty = false;
|
||||
QHash<qint32, qint32> m_peekerToCachedIndex;
|
||||
friend class QXcbEventReader;
|
||||
friend class QXcbEventQueue;
|
||||
|
||||
QByteArray m_xdgCurrentDesktop;
|
||||
};
|
||||
|
|
@ -737,24 +688,6 @@ Q_DECLARE_TYPEINFO(QXcbConnection::TabletData, Q_MOVABLE_TYPE);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
template<typename Functor>
|
||||
xcb_generic_event_t *QXcbConnection::checkEvent(Functor &&filter, bool removeFromQueue)
|
||||
{
|
||||
QXcbEventArray *eventqueue = m_reader->lock();
|
||||
|
||||
for (int i = 0; i < eventqueue->size(); ++i) {
|
||||
xcb_generic_event_t *event = eventqueue->at(i);
|
||||
if (event && filter(event, event->response_type & ~0x80)) {
|
||||
if (removeFromQueue)
|
||||
(*eventqueue)[i] = nullptr;
|
||||
m_reader->unlock();
|
||||
return event;
|
||||
}
|
||||
}
|
||||
m_reader->unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class QXcbConnectionGrabber
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -794,7 +794,7 @@ void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_even
|
|||
{
|
||||
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
|
||||
ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition));
|
||||
while (auto nextEvent = connection()->checkEvent(scanner)) {
|
||||
while (auto nextEvent = connection()->eventQueue()->peek(scanner)) {
|
||||
if (lastEvent != event)
|
||||
free(lastEvent);
|
||||
lastEvent = reinterpret_cast<xcb_client_message_event_t *>(nextEvent);
|
||||
|
|
@ -846,7 +846,7 @@ void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
|
|||
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
|
||||
xcb_generic_event_t *nextEvent;
|
||||
ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus));
|
||||
while ((nextEvent = connection()->checkEvent(scanner))) {
|
||||
while ((nextEvent = connection()->eventQueue()->peek(scanner))) {
|
||||
if (lastEvent != event)
|
||||
free(lastEvent);
|
||||
lastEvent = (xcb_client_message_event_t *)nextEvent;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
#include "qxcbeventqueue.h"
|
||||
#include "qxcbconnection.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QAbstractEventDispatcher>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QXcbEventQueue
|
||||
\internal
|
||||
|
||||
Lock-free event passing:
|
||||
|
||||
The lock-free solution uses a singly-linked list to pass events from the
|
||||
reader thread to the main thread. An atomic operation is used to sync the
|
||||
tail node of the list between threads. The reader thread takes special care
|
||||
when accessing the tail node. It does not dequeue the last node and does not
|
||||
access (read or write) the tail node's 'next' member. This lets the reader
|
||||
add more items at the same time as the main thread is dequeuing nodes from
|
||||
the head. A custom linked list implementation is used, because QLinkedList
|
||||
does not have any thread-safety guarantees and the custom list is more
|
||||
lightweight - no reference counting, back links, etc.
|
||||
|
||||
Memory management:
|
||||
|
||||
In a normally functioning application, XCB plugin won't buffer more than few
|
||||
batches of events, couple events per batch. Instead of constantly calling
|
||||
new / delete, we can create a pool of nodes that we reuse. The main thread
|
||||
uses an atomic operation to sync how many nodes have been restored (available
|
||||
for reuse). If at some point a user application will block the main thread
|
||||
for a long time, we might run out of nodes in the pool. Then we create nodes
|
||||
on a heap. These will be automatically "garbage collected" out of the linked
|
||||
list, once the main thread stops blocking.
|
||||
*/
|
||||
|
||||
QXcbEventQueue::QXcbEventQueue(QXcbConnection *connection)
|
||||
: m_connection(connection)
|
||||
{
|
||||
connect(this, &QXcbEventQueue::eventsPending, m_connection, &QXcbConnection::processXcbEvents, Qt::QueuedConnection);
|
||||
connect(this, &QXcbEventQueue::finished, m_connection, &QXcbConnection::processXcbEvents);
|
||||
|
||||
// Lets init the list with one node, so we don't have to check for
|
||||
// this special case in various places.
|
||||
m_head = m_flushedTail = qXcbEventNodeFactory(nullptr);
|
||||
m_tail.store(m_head, std::memory_order_release);
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
QXcbEventQueue::~QXcbEventQueue()
|
||||
{
|
||||
if (isRunning()) {
|
||||
sendCloseConnectionEvent();
|
||||
wait();
|
||||
}
|
||||
|
||||
while (xcb_generic_event_t *event = takeFirst())
|
||||
free(event);
|
||||
|
||||
if (m_head && m_head->fromHeap)
|
||||
delete m_head; // the deferred node
|
||||
|
||||
qCDebug(lcQpaEventReader) << "nodes on heap:" << m_nodesOnHeap;
|
||||
}
|
||||
|
||||
void QXcbEventQueue::registerEventDispatcher(QAbstractEventDispatcher *dispatcher)
|
||||
{
|
||||
// Flush the xcb connection before the event dispatcher is going to block.
|
||||
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_connection, &QXcbConnection::flush);
|
||||
}
|
||||
|
||||
xcb_generic_event_t *QXcbEventQueue::takeFirst()
|
||||
{
|
||||
if (isEmpty())
|
||||
return nullptr;
|
||||
|
||||
xcb_generic_event_t *event = nullptr;
|
||||
do {
|
||||
event = m_head->event;
|
||||
if (m_head == m_flushedTail) {
|
||||
// defer dequeuing until next successful flush of events
|
||||
if (event) // check if not cleared already by some filter
|
||||
m_head->event = nullptr; // if not, clear it
|
||||
} else {
|
||||
dequeueNode();
|
||||
if (!event)
|
||||
continue; // consumed by filter or deferred node
|
||||
}
|
||||
} while (!isEmpty() && !event);
|
||||
|
||||
m_queueModified = m_peekerIndexCacheDirty = true;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void QXcbEventQueue::dequeueNode()
|
||||
{
|
||||
QXcbEventNode *node = m_head;
|
||||
m_head = m_head->next;
|
||||
if (node->fromHeap)
|
||||
delete node;
|
||||
else
|
||||
m_nodesRestored.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
void QXcbEventQueue::flushBufferedEvents()
|
||||
{
|
||||
m_flushedTail = m_tail.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
QXcbEventNode *QXcbEventQueue::qXcbEventNodeFactory(xcb_generic_event_t *event)
|
||||
{
|
||||
static QXcbEventNode qXcbNodePool[PoolSize];
|
||||
|
||||
if (m_freeNodes == 0) // out of nodes, check if the main thread has released any
|
||||
m_freeNodes = m_nodesRestored.exchange(0, std::memory_order_acquire);
|
||||
|
||||
if (m_freeNodes) {
|
||||
m_freeNodes--;
|
||||
if (m_poolIndex == PoolSize) {
|
||||
// wrap back to the beginning, we always take and restore nodes in-order
|
||||
m_poolIndex = 0;
|
||||
}
|
||||
QXcbEventNode *node = &qXcbNodePool[m_poolIndex++];
|
||||
node->event = event;
|
||||
node->next = nullptr;
|
||||
return node;
|
||||
}
|
||||
|
||||
// the main thread is not flushing events and thus the pool has become empty
|
||||
auto node = new QXcbEventNode(event);
|
||||
node->fromHeap = true;
|
||||
qCDebug(lcQpaEventReader) << "[heap] " << m_nodesOnHeap++;
|
||||
return node;
|
||||
}
|
||||
|
||||
void QXcbEventQueue::run()
|
||||
{
|
||||
xcb_generic_event_t *event = nullptr;
|
||||
xcb_connection_t *connection = m_connection->xcb_connection();
|
||||
QXcbEventNode *tail = m_head;
|
||||
|
||||
auto enqueueEvent = [&tail, this](xcb_generic_event_t *event) {
|
||||
if (!isCloseConnectionEvent(event)) {
|
||||
tail->next = qXcbEventNodeFactory(event);
|
||||
tail = tail->next;
|
||||
m_tail.store(tail, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
while (!m_closeConnectionDetected && (event = xcb_wait_for_event(connection))) {
|
||||
enqueueEvent(event);
|
||||
while (!m_closeConnectionDetected && (event = xcb_poll_for_queued_event(connection)))
|
||||
enqueueEvent(event);
|
||||
|
||||
emit eventsPending();
|
||||
}
|
||||
}
|
||||
|
||||
qint32 QXcbEventQueue::generatePeekerId()
|
||||
{
|
||||
const qint32 peekerId = m_peekerIdSource++;
|
||||
m_peekerToNode.insert(peekerId, nullptr);
|
||||
return peekerId;
|
||||
}
|
||||
|
||||
bool QXcbEventQueue::removePeekerId(qint32 peekerId)
|
||||
{
|
||||
const auto it = m_peekerToNode.find(peekerId);
|
||||
if (it == m_peekerToNode.constEnd()) {
|
||||
qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
|
||||
return false;
|
||||
}
|
||||
m_peekerToNode.erase(it);
|
||||
if (m_peekerToNode.isEmpty()) {
|
||||
m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
|
||||
m_peekerIndexCacheDirty = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
|
||||
PeekOptions option, qint32 peekerId)
|
||||
{
|
||||
const bool peekerIdProvided = peekerId != -1;
|
||||
auto peekerToNodeIt = m_peekerToNode.find(peekerId);
|
||||
|
||||
if (peekerIdProvided && peekerToNodeIt == m_peekerToNode.constEnd()) {
|
||||
qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool useCache = option.testFlag(PeekOption::PeekFromCachedIndex);
|
||||
if (useCache && !peekerIdProvided) {
|
||||
qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (peekerIdProvided && m_peekerIndexCacheDirty) {
|
||||
for (auto &node : m_peekerToNode) // reset cache
|
||||
node = nullptr;
|
||||
m_peekerIndexCacheDirty = false;
|
||||
}
|
||||
|
||||
flushBufferedEvents();
|
||||
if (isEmpty())
|
||||
return false;
|
||||
|
||||
const auto startNode = [this, useCache, peekerToNodeIt]() -> QXcbEventNode * {
|
||||
if (useCache) {
|
||||
const QXcbEventNode *cachedNode = peekerToNodeIt.value();
|
||||
if (!cachedNode)
|
||||
return m_head; // cache was reset
|
||||
if (cachedNode == m_flushedTail)
|
||||
return nullptr; // no new events since the last call
|
||||
return cachedNode->next;
|
||||
}
|
||||
return m_head;
|
||||
}();
|
||||
|
||||
if (!startNode)
|
||||
return false;
|
||||
|
||||
// A peeker may call QCoreApplication::processEvents(), which will cause
|
||||
// QXcbConnection::processXcbEvents() to modify the queue we are currently
|
||||
// looping through;
|
||||
m_queueModified = false;
|
||||
bool result = false;
|
||||
|
||||
QXcbEventNode *node = startNode;
|
||||
do {
|
||||
xcb_generic_event_t *event = node->event;
|
||||
if (event && peeker(event, peekerData)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
if (node == m_flushedTail)
|
||||
break;
|
||||
node = node->next;
|
||||
} while (!m_queueModified);
|
||||
|
||||
// Update the cached index if the queue was not modified, and hence the
|
||||
// cache is still valid.
|
||||
if (peekerIdProvided && node != startNode && !m_queueModified) {
|
||||
// Before updating, make sure that a peeker callback did not remove
|
||||
// the peeker id.
|
||||
peekerToNodeIt = m_peekerToNode.find(peekerId);
|
||||
if (peekerToNodeIt != m_peekerToNode.constEnd())
|
||||
*peekerToNodeIt = node; // id still in the cache, update node
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void QXcbEventQueue::sendCloseConnectionEvent() const
|
||||
{
|
||||
// A hack to close XCB connection. Apparently XCB does not have any APIs for this?
|
||||
xcb_client_message_event_t event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
|
||||
xcb_connection_t *c = m_connection->xcb_connection();
|
||||
const xcb_window_t window = xcb_generate_id(c);
|
||||
xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_connection->m_setup);
|
||||
xcb_screen_t *screen = it.data;
|
||||
xcb_create_window(c, XCB_COPY_FROM_PARENT,
|
||||
window, screen->root,
|
||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
|
||||
screen->root_visual, 0, nullptr);
|
||||
|
||||
event.response_type = XCB_CLIENT_MESSAGE;
|
||||
event.format = 32;
|
||||
event.sequence = 0;
|
||||
event.window = window;
|
||||
event.type = m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION);
|
||||
event.data.data32[0] = 0;
|
||||
|
||||
xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
|
||||
xcb_destroy_window(c, window);
|
||||
xcb_flush(c);
|
||||
}
|
||||
|
||||
bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
|
||||
{
|
||||
if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
|
||||
auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
|
||||
if (clientMessage->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
|
||||
m_closeConnectionDetected = true;
|
||||
}
|
||||
return m_closeConnectionDetected;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
#ifndef QXCBEVENTQUEUE_H
|
||||
#define QXCBEVENTQUEUE_H
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QHash>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
struct QXcbEventNode {
|
||||
QXcbEventNode(xcb_generic_event_t *e = nullptr)
|
||||
: event(e) { }
|
||||
|
||||
xcb_generic_event_t *event;
|
||||
QXcbEventNode *next = nullptr;
|
||||
bool fromHeap = false;
|
||||
};
|
||||
|
||||
class QXcbConnection;
|
||||
class QAbstractEventDispatcher;
|
||||
|
||||
class QXcbEventQueue : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QXcbEventQueue(QXcbConnection *connection);
|
||||
~QXcbEventQueue();
|
||||
|
||||
enum { PoolSize = 100 }; // 2.4 kB with 100 nodes
|
||||
|
||||
enum PeekOption {
|
||||
PeekDefault = 0, // see qx11info_x11.h for docs
|
||||
PeekFromCachedIndex = 1,
|
||||
PeekRetainMatch = 2,
|
||||
PeekRemoveMatch = 3,
|
||||
PeekRemoveMatchContinue = 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(PeekOptions, PeekOption)
|
||||
|
||||
void run() override;
|
||||
|
||||
void registerEventDispatcher(QAbstractEventDispatcher *dispatcher);
|
||||
|
||||
bool isEmpty() const { return m_head == m_flushedTail && !m_head->event; }
|
||||
xcb_generic_event_t *takeFirst();
|
||||
void flushBufferedEvents();
|
||||
|
||||
// ### peek() and peekEventQueue() could be unified. Note that peekEventQueue()
|
||||
// is public API exposed via QX11Extras/QX11Info.
|
||||
template<typename Peeker>
|
||||
xcb_generic_event_t *peek(Peeker &&peeker) {
|
||||
return peek(PeekRemoveMatch, std::forward<Peeker>(peeker));
|
||||
}
|
||||
template<typename Peeker>
|
||||
inline xcb_generic_event_t *peek(PeekOption config, Peeker &&peeker);
|
||||
|
||||
qint32 generatePeekerId();
|
||||
bool removePeekerId(qint32 peekerId);
|
||||
|
||||
using PeekerCallback = bool (*)(xcb_generic_event_t *event, void *peekerData);
|
||||
bool peekEventQueue(PeekerCallback peeker, void *peekerData = nullptr,
|
||||
PeekOptions option = PeekDefault, qint32 peekerId = -1);
|
||||
signals:
|
||||
void eventsPending();
|
||||
|
||||
private:
|
||||
QXcbEventNode *qXcbEventNodeFactory(xcb_generic_event_t *event);
|
||||
void dequeueNode();
|
||||
|
||||
void sendCloseConnectionEvent() const;
|
||||
bool isCloseConnectionEvent(const xcb_generic_event_t *event);
|
||||
|
||||
QXcbEventNode *m_head = nullptr;
|
||||
QXcbEventNode *m_flushedTail = nullptr;
|
||||
std::atomic<QXcbEventNode *> m_tail { nullptr };
|
||||
std::atomic_uint m_nodesRestored { 0 };
|
||||
|
||||
QXcbConnection *m_connection = nullptr;
|
||||
bool m_closeConnectionDetected = false;
|
||||
|
||||
uint m_freeNodes = PoolSize;
|
||||
uint m_poolIndex = 0;
|
||||
|
||||
qint32 m_peekerIdSource = 0;
|
||||
bool m_queueModified = false;
|
||||
bool m_peekerIndexCacheDirty = false;
|
||||
QHash<qint32, QXcbEventNode *> m_peekerToNode;
|
||||
|
||||
// debug stats
|
||||
quint64 m_nodesOnHeap = 0;
|
||||
};
|
||||
|
||||
template<typename Peeker>
|
||||
xcb_generic_event_t *QXcbEventQueue::peek(PeekOption option, Peeker &&peeker)
|
||||
{
|
||||
flushBufferedEvents();
|
||||
if (isEmpty())
|
||||
return nullptr;
|
||||
|
||||
QXcbEventNode *node = m_head;
|
||||
do {
|
||||
xcb_generic_event_t *event = node->event;
|
||||
if (event && peeker(event, event->response_type & ~0x80)) {
|
||||
if (option == PeekRemoveMatch || option == PeekRemoveMatchContinue)
|
||||
node->event = nullptr;
|
||||
if (option != PeekRemoveMatchContinue)
|
||||
return event;
|
||||
}
|
||||
if (node == m_flushedTail)
|
||||
break;
|
||||
node = node->next;
|
||||
} while (true);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -46,6 +46,7 @@
|
|||
#include "qxcbbackingstore.h"
|
||||
#include "qxcbnativeinterface.h"
|
||||
#include "qxcbclipboard.h"
|
||||
#include "qxcbeventqueue.h"
|
||||
#if QT_CONFIG(draganddrop)
|
||||
#include "qxcbdrag.h"
|
||||
#endif
|
||||
|
|
@ -344,7 +345,7 @@ QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
|
|||
{
|
||||
QAbstractEventDispatcher *dispatcher = createUnixEventDispatcher();
|
||||
for (int i = 0; i < m_connections.size(); i++)
|
||||
m_connections[i]->eventReader()->registerEventDispatcher(dispatcher);
|
||||
m_connections[i]->eventQueue()->registerEventDispatcher(dispatcher);
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1541,7 +1541,8 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type,
|
|||
} else {
|
||||
m_isAutoRepeat = false;
|
||||
// Look at the next event in the queue to see if we are auto-repeating.
|
||||
connection()->checkEvent([this, time, code](xcb_generic_event_t *event, int type) {
|
||||
connection()->eventQueue()->peek(QXcbEventQueue::PeekRetainMatch,
|
||||
[this, time, code](xcb_generic_event_t *event, int type) {
|
||||
if (type == XCB_KEY_PRESS) {
|
||||
auto keyPress = reinterpret_cast<xcb_key_press_event_t *>(event);
|
||||
m_isAutoRepeat = keyPress->time == time && keyPress->detail == code;
|
||||
|
|
@ -1549,7 +1550,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type,
|
|||
m_autoRepeatCode = code;
|
||||
}
|
||||
return true;
|
||||
}, false /* removeFromQueue */);
|
||||
});
|
||||
}
|
||||
|
||||
bool filtered = false;
|
||||
|
|
|
|||
|
|
@ -437,20 +437,20 @@ void QXcbNativeInterface::setAppUserTime(QScreen* screen, xcb_timestamp_t time)
|
|||
qint32 QXcbNativeInterface::generatePeekerId()
|
||||
{
|
||||
QXcbIntegration *integration = QXcbIntegration::instance();
|
||||
return integration->defaultConnection()->generatePeekerId();
|
||||
return integration->defaultConnection()->eventQueue()->generatePeekerId();
|
||||
}
|
||||
|
||||
bool QXcbNativeInterface::removePeekerId(qint32 peekerId)
|
||||
{
|
||||
QXcbIntegration *integration = QXcbIntegration::instance();
|
||||
return integration->defaultConnection()->removePeekerId(peekerId);
|
||||
return integration->defaultConnection()->eventQueue()->removePeekerId(peekerId);
|
||||
}
|
||||
|
||||
bool QXcbNativeInterface::peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData,
|
||||
QXcbConnection::PeekOptions option, qint32 peekerId)
|
||||
bool QXcbNativeInterface::peekEventQueue(QXcbEventQueue::PeekerCallback peeker, void *peekerData,
|
||||
QXcbEventQueue::PeekOptions option, qint32 peekerId)
|
||||
{
|
||||
QXcbIntegration *integration = QXcbIntegration::instance();
|
||||
return integration->defaultConnection()->peekEventQueue(peeker, peekerData, option, peekerId);
|
||||
return integration->defaultConnection()->eventQueue()->peekEventQueue(peeker, peekerData, option, peekerId);
|
||||
}
|
||||
|
||||
void QXcbNativeInterface::setStartupId(const char *data)
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ public:
|
|||
|
||||
static qint32 generatePeekerId();
|
||||
static bool removePeekerId(qint32 peekerId);
|
||||
static bool peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData = nullptr,
|
||||
QXcbConnection::PeekOptions option = QXcbConnection::PeekDefault,
|
||||
static bool peekEventQueue(QXcbEventQueue::PeekerCallback peeker, void *peekerData = nullptr,
|
||||
QXcbEventQueue::PeekOptions option = QXcbEventQueue::PeekDefault,
|
||||
qint32 peekerId = -1);
|
||||
|
||||
Q_INVOKABLE QString dumpConnectionNativeWindows(const QXcbConnection *connection, WId root) const;
|
||||
|
|
|
|||
|
|
@ -1818,21 +1818,20 @@ void QXcbWindow::handleExposeEvent(const xcb_expose_event_t *event)
|
|||
m_exposeRegion |= rect;
|
||||
|
||||
bool pending = true;
|
||||
xcb_generic_event_t *e = nullptr;
|
||||
do { // compress expose events
|
||||
e = connection()->checkEvent([this, &pending](xcb_generic_event_t *event, int type) {
|
||||
if (type != XCB_EXPOSE)
|
||||
return false;
|
||||
auto expose = reinterpret_cast<xcb_expose_event_t *>(event);
|
||||
if (expose->window != m_window)
|
||||
return false;
|
||||
if (expose->count == 0)
|
||||
pending = false;
|
||||
m_exposeRegion |= QRect(expose->x, expose->y, expose->width, expose->height);
|
||||
return true;
|
||||
});
|
||||
free(e);
|
||||
} while (e);
|
||||
|
||||
connection()->eventQueue()->peek(QXcbEventQueue::PeekRemoveMatchContinue,
|
||||
[this, &pending](xcb_generic_event_t *event, int type) {
|
||||
if (type != XCB_EXPOSE)
|
||||
return false;
|
||||
auto expose = reinterpret_cast<xcb_expose_event_t *>(event);
|
||||
if (expose->window != m_window)
|
||||
return false;
|
||||
if (expose->count == 0)
|
||||
pending = false;
|
||||
m_exposeRegion |= QRect(expose->x, expose->y, expose->width, expose->height);
|
||||
free(expose);
|
||||
return true;
|
||||
});
|
||||
|
||||
// if count is non-zero there are more expose events pending
|
||||
if (event->count == 0 || !pending) {
|
||||
|
|
@ -2152,13 +2151,11 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y,
|
|||
return;
|
||||
|
||||
// check if enter event is buffered
|
||||
auto event = connection()->checkEvent([](xcb_generic_event_t *event, int type) {
|
||||
auto event = connection()->eventQueue()->peek([](xcb_generic_event_t *event, int type) {
|
||||
if (type != XCB_ENTER_NOTIFY)
|
||||
return false;
|
||||
auto enter = reinterpret_cast<xcb_enter_notify_event_t *>(event);
|
||||
if (ignoreEnterEvent(enter->mode, enter->detail))
|
||||
return false;
|
||||
return true;
|
||||
return !ignoreEnterEvent(enter->mode, enter->detail);
|
||||
});
|
||||
auto enter = reinterpret_cast<xcb_enter_notify_event_t *>(event);
|
||||
QXcbWindow *enterWindow = enter ? connection()->platformWindowFromId(enter->event) : nullptr;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ SOURCES = \
|
|||
qxcbcursor.cpp \
|
||||
qxcbimage.cpp \
|
||||
qxcbxsettings.cpp \
|
||||
qxcbsystemtraytracker.cpp
|
||||
qxcbsystemtraytracker.cpp \
|
||||
qxcbeventqueue.cpp
|
||||
|
||||
HEADERS = \
|
||||
qxcbclipboard.h \
|
||||
|
|
@ -45,7 +46,8 @@ HEADERS = \
|
|||
qxcbimage.h \
|
||||
qxcbxsettings.h \
|
||||
qxcbsystemtraytracker.h \
|
||||
qxcbxkbcommon.h
|
||||
qxcbxkbcommon.h \
|
||||
qxcbeventqueue.h
|
||||
|
||||
qtConfig(draganddrop) {
|
||||
SOURCES += qxcbdrag.cpp
|
||||
|
|
|
|||
Loading…
Reference in New Issue