diff options
author | Liang Qi <liang.qi@qt.io> | 2018-10-25 07:21:05 +0200 |
---|---|---|
committer | Liang Qi <liang.qi@qt.io> | 2018-10-25 07:21:53 +0200 |
commit | e28e91ae99b8c3859899e04cc9370534c7c7b86d (patch) | |
tree | cca81b1e745be4f25aab78e8e917c2324594e539 /src/plugins | |
parent | 5ea233ca6782eb27adf596515cb66ef3dadc1d5e (diff) | |
parent | ebfad73b4e44fe6db8059200da105b4b87888718 (diff) |
Merge remote-tracking branch 'origin/5.12' into dev
Conflicts:
src/corelib/animation/qpropertyanimation.cpp
src/gui/image/qicon.cpp
tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp
Change-Id: I3698172b7b44ebb487cb38f50fd2c4a9f8a35b21
Diffstat (limited to 'src/plugins')
56 files changed, 3131 insertions, 2042 deletions
diff --git a/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp b/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp new file mode 100644 index 0000000000..ce82533c3e --- /dev/null +++ b/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins 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$ +** +****************************************************************************/ + +//! [0] + QQuickView *view = new QQuickView(parent); + view->create(); + QGuiApplication::platformNativeInterface()->setWindowProperty(view->handle(), "qnxWindowGroup", + group); +//! [0] + +//! [1] + QQuickView *view = new QQuickView(parent); + view->create(); + QGuiApplication::platformNativeInterface()->setWindowProperty(view->handle(), "qnxWindowGroup", + QVariant()); +//! [1] diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 1e1b3907ed..1cd77a22e4 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -414,7 +414,8 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) // have the same effect as an update. // Now we are ready to associate the view with the context - if ((m_context.view = view) != view) { + m_context.view = view; + if (m_context.view != view) { qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context; m_updateObservers.clear(); return false; diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h index 20fc741fb8..c842b08d52 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -119,7 +119,6 @@ private: QString m_text; QIcon m_icon; QPointer<QCocoaMenu> m_menu; - QFont m_font; MenuRole m_role; MenuRole m_detectedRole; #ifndef QT_NO_SHORTCUT diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index 21faa6d985..e54b6284e5 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -173,7 +173,7 @@ void QCocoaMenuItem::setIsSeparator(bool isSeparator) void QCocoaMenuItem::setFont(const QFont &font) { - m_font = font; + Q_UNUSED(font) } void QCocoaMenuItem::setRole(MenuRole role) @@ -319,21 +319,7 @@ NSMenuItem *QCocoaMenuItem::sync() text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")"); #endif - QString finalString = QPlatformTheme::removeMnemonics(text); - bool useAttributedTitle = false; - // Cocoa Font and title - if (m_font.resolve()) { - NSFont *customMenuFont = [NSFont fontWithName:m_font.family().toNSString() - size:m_font.pointSize()]; - if (customMenuFont) { - NSAttributedString *str = [[[NSAttributedString alloc] initWithString:finalString.toNSString() - attributes:@{NSFontAttributeName: customMenuFont}] autorelease]; - m_native.attributedTitle = str; - useAttributedTitle = true; - } - } - if (!useAttributedTitle) - m_native.title = finalString.toNSString(); + m_native.title = QPlatformTheme::removeMnemonics(text).toNSString(); #ifndef QT_NO_SHORTCUT if (accel.count() == 1) { diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.h b/src/plugins/platforms/cocoa/qcocoaprintdevice.h index 20b27f3286..d267343b0e 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.h +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.h @@ -98,7 +98,9 @@ protected: void loadOutputBins() const override; void loadDuplexModes() const override; void loadColorModes() const override; +#if QT_CONFIG(mimetype) void loadMimeTypes() const override; +#endif private: QPageSize createPageSize(const PMPaper &paper) const; diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm index 24ec7ca9a4..7605dc9d1a 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm @@ -39,7 +39,9 @@ #include "qcocoaprintdevice.h" +#if QT_CONFIG(mimetype) #include <QtCore/qmimedatabase.h> +#endif #include <qdebug.h> QT_BEGIN_NAMESPACE @@ -417,6 +419,7 @@ QPrint::ColorMode QCocoaPrintDevice::defaultColorMode() const return QPrint::GrayScale; } +#if QT_CONFIG(mimetype) void QCocoaPrintDevice::loadMimeTypes() const { // TODO Check how settings affect returned list @@ -438,6 +441,7 @@ void QCocoaPrintDevice::loadMimeTypes() const } m_haveMimeTypes = true; } +#endif // mimetype bool QCocoaPrintDevice::openPpdFile() { diff --git a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm index c1711e7cd4..aef2c6cf48 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm @@ -75,8 +75,9 @@ QPalette * qt_mac_createSystemPalette() palette->setBrush(QPalette::Disabled, QPalette::Text, dark); palette->setBrush(QPalette::Disabled, QPalette::ButtonText, dark); palette->setBrush(QPalette::Disabled, QPalette::Base, backgroundBrush); - palette->setBrush(QPalette::Active, QPalette::Base, backgroundBrush); - palette->setBrush(QPalette::Inactive, QPalette::Base, backgroundBrush); + QBrush textBackgroundBrush = qt_mac_toQBrush([NSColor textBackgroundColor]); + palette->setBrush(QPalette::Active, QPalette::Base, textBackgroundBrush); + palette->setBrush(QPalette::Inactive, QPalette::Base, textBackgroundBrush); palette->setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191)); palette->setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191)); palette->setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191)); @@ -202,7 +203,7 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor])); } else { baseColors = [NSColor controlAlternatingRowBackgroundColors]; - activeHighlightColor = [NSColor selectedControlColor]; + activeHighlightColor = [NSColor alternateSelectedControlColor]; pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, pal.brush(QPalette::Active, QPalette::Text)); } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index b2d1a80097..07c1e8ca3a 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -1720,6 +1720,14 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) [window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground]; window.titlebarAppearsTransparent = YES; + // Setting titlebarAppearsTransparent to YES means that the border thickness has to account + // for the title bar height as well, otherwise sheets will not be presented at the correct + // position, which should be (title bar height + top content border size). + const NSRect frameRect = window.frame; + const NSRect contentRect = [window contentRectForFrameRect:frameRect]; + const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height; + effectiveTopContentBorderThickness += titlebarHeight; + [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge]; [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index 28db532ddc..ad751279bb 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -53,7 +53,7 @@ qtMods |= Qt::AltModifier; if (modifierFlags & NSEventModifierFlagCommand) qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; - if (modifierFlags & NSEventModifierFlagCommand) + if (modifierFlags & NSEventModifierFlagNumericPad) qtMods |= Qt::KeypadModifier; return qtMods; } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index 0cbb494c2f..24f82e7843 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -149,16 +149,32 @@ QPlatformCursor *QEglFSKmsGbmScreen::cursor() const } } -gbm_surface *QEglFSKmsGbmScreen::createSurface() +gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig) { if (!m_gbm_surface) { - uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format); - qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s with format 0x%x", qPrintable(name()), gbmFormat); - m_gbm_surface = gbm_surface_create(static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice(), + qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s", qPrintable(name())); + + const auto gbmDevice = static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice(); + EGLint native_format = -1; + EGLBoolean success = eglGetConfigAttrib(display(), eglConfig, EGL_NATIVE_VISUAL_ID, &native_format); + qCDebug(qLcEglfsKmsDebug) << "Got native format" << hex << native_format << dec << "from eglGetConfigAttrib() with return code" << bool(success); + + if (success) + m_gbm_surface = gbm_surface_create(gbmDevice, + rawGeometry().width(), + rawGeometry().height(), + native_format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + + if (!m_gbm_surface) { // fallback for older drivers + uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format); + qCDebug(qLcEglfsKmsDebug, "Could not create surface with EGL_NATIVE_VISUAL_ID, falling back to format %x", gbmFormat); + m_gbm_surface = gbm_surface_create(gbmDevice, rawGeometry().width(), rawGeometry().height(), gbmFormat, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + } } return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface() } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h index f5a2122723..b94f44b7b1 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h @@ -59,7 +59,7 @@ public: QPlatformCursor *cursor() const override; - gbm_surface *createSurface(); + gbm_surface *createSurface(EGLConfig eglConfig); void resetSurface(); void initCloning(QPlatformScreen *screenThisScreenClones, diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp index 110a592dec..65a7c4f38a 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp @@ -53,7 +53,7 @@ void QEglFSKmsGbmWindow::resetSurface() m_config = QEglFSDeviceIntegration::chooseConfig(display, platformFormat); m_format = q_glFormatFromConfig(display, m_config, platformFormat); // One fullscreen window per screen -> the native window is simply the gbm_surface the screen created. - m_window = reinterpret_cast<EGLNativeWindowType>(gbmScreen->createSurface()); + m_window = reinterpret_cast<EGLNativeWindowType>(gbmScreen->createSurface(m_config)); PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface = nullptr; const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); diff --git a/src/plugins/platforms/qnx/qqnxwindow.cpp b/src/plugins/platforms/qnx/qqnxwindow.cpp index 7c98b29348..7644e28b44 100644 --- a/src/plugins/platforms/qnx/qqnxwindow.cpp +++ b/src/plugins/platforms/qnx/qqnxwindow.cpp @@ -132,22 +132,12 @@ DECLARE_DEBUG_VAR(statistics) setWindowProperty function of the native interface to set the \e qnxWindowGroup property to the desired value, for example: - \code - QQuickView *view = new QQuickView(parent); - view->create(); - QGuiApplication::platformNativeInterface()->setWindowProperty(view->handle(), "qnxWindowGroup", - group); - \endcode + \snippet code/src_plugins_platforms_qnx_qqnxwindow.cpp 0 To leave the current window group, one passes a null value for the property value, for example: - \code - QQuickView *view = new QQuickView(parent); - view->create(); - QGuiApplication::platformNativeInterface()->setWindowProperty(view->handle(), "qnxWindowGroup", - QVariant()); - \endcode + \snippet code/src_plugins_platforms_qnx_qqnxwindow.cpp 1 \section1 Window Id diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp index cc48c15b64..23251fd610 100644 --- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp @@ -50,7 +50,7 @@ using namespace emscripten; // the existing switching code in QtGui, but for now do it here. static bool g_usePlatformMacCtrlMetaSwitching = false; -bool g_useNaturalScrolling = false; +bool g_useNaturalScrolling = true; // natural scrolling is default on linux/windows void setNaturalScrolling(bool use) { g_useNaturalScrolling = use; @@ -98,7 +98,7 @@ QWasmEventTranslator::QWasmEventTranslator(QObject *parent) g_usePlatformMacCtrlMetaSwitching = (platform == MacOSPlatform); if (platform == MacOSPlatform) { - g_useNaturalScrolling = true; //make this default on macOS + g_useNaturalScrolling = false; // make this !default on macOS EM_ASM( if (window.safari !== undefined) {//this only works on safari Module["canvas"].addEventListener('wheel', mouseWheelEvent); @@ -492,6 +492,9 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh if (wheelEvent->deltaX != 0) pixelDelta.setX(wheelEvent->deltaX * scrollFactor); QWindowSystemInterface::handleWheelEvent(window2, timestamp, localPoint, globalPoint, QPoint(), pixelDelta, modifiers); + + QWasmEventDispatcher::maintainTimers(); + return 1; } diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 253aeb9d76..1f80ac5872 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -148,7 +148,7 @@ static inline bool sessionManagerInteractionBlocked() { return false; } static inline int windowDpiAwareness(HWND hwnd) { - return QWindowsContext::user32dll.getWindowDpiAwarenessContext && QWindowsContext::user32dll.getWindowDpiAwarenessContext + return QWindowsContext::user32dll.getWindowDpiAwarenessContext && QWindowsContext::user32dll.getAwarenessFromDpiAwarenessContext ? QWindowsContext::user32dll.getAwarenessFromDpiAwarenessContext(QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd)) : -1; } @@ -213,6 +213,7 @@ void QWindowsUser32DLL::init() enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling"); getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext"); getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext"); + systemParametersInfoForDpi = (SystemParametersInfoForDpi)library.resolve("SystemParametersInfoForDpi"); } } @@ -916,6 +917,45 @@ QByteArray QWindowsContext::comErrorString(HRESULT hr) return result; } +bool QWindowsContext::systemParametersInfo(unsigned action, unsigned param, void *out, + unsigned dpi) +{ + const BOOL result = QWindowsContext::user32dll.systemParametersInfoForDpi != nullptr && dpi != 0 + ? QWindowsContext::user32dll.systemParametersInfoForDpi(action, param, out, 0, dpi) + : SystemParametersInfo(action, param, out, 0); + return result == TRUE; +} + +bool QWindowsContext::systemParametersInfoForScreen(unsigned action, unsigned param, void *out, + const QPlatformScreen *screen) +{ + return systemParametersInfo(action, param, out, screen ? screen->logicalDpi().first : 0); +} + +bool QWindowsContext::systemParametersInfoForWindow(unsigned action, unsigned param, void *out, + const QPlatformWindow *win) +{ + return systemParametersInfoForScreen(action, param, out, win ? win->screen() : nullptr); +} + +bool QWindowsContext::nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi) +{ + memset(ncm, 0, sizeof(NONCLIENTMETRICS)); + ncm->cbSize = sizeof(NONCLIENTMETRICS); + return systemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm->cbSize, ncm, dpi); +} + +bool QWindowsContext::nonClientMetricsForScreen(NONCLIENTMETRICS *ncm, + const QPlatformScreen *screen) +{ + return nonClientMetrics(ncm, screen ? screen->logicalDpi().first : 0); +} + +bool QWindowsContext::nonClientMetricsForWindow(NONCLIENTMETRICS *ncm, const QPlatformWindow *win) +{ + return nonClientMetricsForScreen(ncm, win ? win->screen() : nullptr); +} + static inline QWindowsInputContext *windowsInputContext() { return qobject_cast<QWindowsInputContext *>(QWindowsIntegration::instance()->inputContext()); @@ -960,6 +1000,7 @@ static inline bool isInputMessage(UINT m) case WM_IME_STARTCOMPOSITION: case WM_IME_ENDCOMPOSITION: case WM_IME_COMPOSITION: + case WM_INPUT: case WM_TOUCH: case WM_MOUSEHOVER: case WM_MOUSELEAVE: diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 19e9c26130..fd6c72668c 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -70,6 +70,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon) class QWindow; class QPlatformScreen; +class QPlatformWindow; class QWindowsMenuBar; class QWindowsScreenManager; class QWindowsTabletSupport; @@ -104,6 +105,7 @@ struct QWindowsUser32DLL typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND); typedef int (WINAPI *GetWindowDpiAwarenessContext)(HWND); typedef int (WINAPI *GetAwarenessFromDpiAwarenessContext)(int); + typedef BOOL (WINAPI *SystemParametersInfoForDpi)(UINT, UINT, PVOID, UINT, UINT); // Windows pointer functions (Windows 8 or later). EnableMouseInPointer enableMouseInPointer = nullptr; @@ -132,6 +134,7 @@ struct QWindowsUser32DLL EnableNonClientDpiScaling enableNonClientDpiScaling = nullptr; GetWindowDpiAwarenessContext getWindowDpiAwarenessContext = nullptr; GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext = nullptr; + SystemParametersInfoForDpi systemParametersInfoForDpi = nullptr; }; // Shell scaling library (Windows 8.1 onwards) @@ -237,6 +240,17 @@ public: bool asyncExpose() const; void setAsyncExpose(bool value); + static bool systemParametersInfo(unsigned action, unsigned param, void *out, unsigned dpi = 0); + static bool systemParametersInfoForScreen(unsigned action, unsigned param, void *out, + const QPlatformScreen *screen = nullptr); + static bool systemParametersInfoForWindow(unsigned action, unsigned param, void *out, + const QPlatformWindow *win = nullptr); + static bool nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi = 0); + static bool nonClientMetricsForScreen(NONCLIENTMETRICS *ncm, + const QPlatformScreen *screen = nullptr); + static bool nonClientMetricsForWindow(NONCLIENTMETRICS *ncm, + const QPlatformWindow *win = nullptr); + static DWORD readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue); QTouchDevice *touchDevice() const; diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index b7d225cb00..ee82b2f022 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -652,6 +652,7 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, */ bool QWindowsDrag::m_canceled = false; +bool QWindowsDrag::m_dragging = false; QWindowsDrag::QWindowsDrag() = default; @@ -699,7 +700,10 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag) const DWORD allowedEffects = translateToWinDragEffects(possibleActions); qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x" << hex << int(possibleActions) << "effects=0x" << allowedEffects << dec; + // Indicate message handlers we are in DoDragDrop() event loop. + QWindowsDrag::m_dragging = true; const HRESULT r = DoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect); + QWindowsDrag::m_dragging = false; const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect(); if (r == DRAGDROP_S_DROP) { if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) { diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h index f116e50cbf..5f30c59882 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.h +++ b/src/plugins/platforms/windows/qwindowsdrag.h @@ -92,6 +92,7 @@ public: static QWindowsDrag *instance(); void cancelDrag() override { QWindowsDrag::m_canceled = true; } static bool isCanceled() { return QWindowsDrag::m_canceled; } + static bool isDragging() { return QWindowsDrag::m_dragging; } IDataObject *dropDataObject() const { return m_dropDataObject; } void setDropDataObject(IDataObject *dataObject) { m_dropDataObject = dataObject; } @@ -102,6 +103,7 @@ public: private: static bool m_canceled; + static bool m_dragging; QWindowsDropMimeData m_dropData; IDataObject *m_dropDataObject = nullptr; diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp index c1c275144f..fcdd179a31 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -207,26 +207,26 @@ static inline MouseEvent eventFromMsg(const MSG &msg) return {QEvent::MouseButtonPress, Qt::LeftButton}; case WM_LBUTTONUP: return {QEvent::MouseButtonRelease, Qt::LeftButton}; - case WM_LBUTTONDBLCLK: - return {QEvent::MouseButtonDblClick, Qt::LeftButton}; + case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press + return {QEvent::MouseButtonPress, Qt::LeftButton}; case WM_MBUTTONDOWN: return {QEvent::MouseButtonPress, Qt::MidButton}; case WM_MBUTTONUP: return {QEvent::MouseButtonRelease, Qt::MidButton}; case WM_MBUTTONDBLCLK: - return {QEvent::MouseButtonDblClick, Qt::MidButton}; + return {QEvent::MouseButtonPress, Qt::MidButton}; case WM_RBUTTONDOWN: return {QEvent::MouseButtonPress, Qt::RightButton}; case WM_RBUTTONUP: return {QEvent::MouseButtonRelease, Qt::RightButton}; case WM_RBUTTONDBLCLK: - return {QEvent::MouseButtonDblClick, Qt::RightButton}; + return {QEvent::MouseButtonPress, Qt::RightButton}; case WM_XBUTTONDOWN: return {QEvent::MouseButtonPress, extraButton(msg.wParam)}; case WM_XBUTTONUP: return {QEvent::MouseButtonRelease, extraButton(msg.wParam)}; case WM_XBUTTONDBLCLK: - return {QEvent::MouseButtonDblClick, extraButton(msg.wParam)}; + return {QEvent::MouseButtonPress, extraButton(msg.wParam)}; case WM_NCMOUSEMOVE: return {QEvent::NonClientAreaMouseMove, Qt::NoButton}; case WM_NCLBUTTONDOWN: @@ -234,19 +234,19 @@ static inline MouseEvent eventFromMsg(const MSG &msg) case WM_NCLBUTTONUP: return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton}; case WM_NCLBUTTONDBLCLK: - return {QEvent::NonClientAreaMouseButtonDblClick, Qt::LeftButton}; + return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton}; case WM_NCMBUTTONDOWN: return {QEvent::NonClientAreaMouseButtonPress, Qt::MidButton}; case WM_NCMBUTTONUP: return {QEvent::NonClientAreaMouseButtonRelease, Qt::MidButton}; case WM_NCMBUTTONDBLCLK: - return {QEvent::NonClientAreaMouseButtonDblClick, Qt::MidButton}; + return {QEvent::NonClientAreaMouseButtonPress, Qt::MidButton}; case WM_NCRBUTTONDOWN: return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton}; case WM_NCRBUTTONUP: return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton}; case WM_NCRBUTTONDBLCLK: - return {QEvent::NonClientAreaMouseButtonDblClick, Qt::RightButton}; + return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton}; default: // WM_MOUSELEAVE break; } diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index c5acc38e7c..21dc0cd577 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -50,6 +50,9 @@ #include "qwindowswindow.h" #include "qwindowsintegration.h" #include "qwindowsscreen.h" +#if QT_CONFIG(draganddrop) +# include "qwindowsdrag.h" +#endif #include <qpa/qwindowsysteminterface.h> #include <QtGui/qguiapplication.h> @@ -60,6 +63,7 @@ #include <QtCore/qvarlengtharray.h> #include <QtCore/qloggingcategory.h> #include <QtCore/qoperatingsystemversion.h> +#include <QtCore/qqueue.h> #include <algorithm> @@ -75,6 +79,111 @@ enum { QT_PT_TOUCHPAD = 5, // MinGW is missing PT_TOUCHPAD }; +struct PointerTouchEventInfo { + QPointer<QWindow> window; + QList<QWindowSystemInterface::TouchPoint> points; + Qt::KeyboardModifiers modifiers; +}; + +struct PointerTabletEventInfo { + QPointer<QWindow> window; + QPointF local; + QPointF global; + int device; + int pointerType; + Qt::MouseButtons buttons; + qreal pressure; + int xTilt; + int yTilt; + qreal tangentialPressure; + qreal rotation; + int z; + qint64 uid; + Qt::KeyboardModifiers modifiers; +}; + +static QQueue<PointerTouchEventInfo> touchEventQueue; +static QQueue<PointerTabletEventInfo> tabletEventQueue; + +static void enqueueTouchEvent(QWindow *window, + const QList<QWindowSystemInterface::TouchPoint> &points, + Qt::KeyboardModifiers modifiers) +{ + PointerTouchEventInfo eventInfo; + eventInfo.window = window; + eventInfo.points = points; + eventInfo.modifiers = modifiers; + touchEventQueue.enqueue(eventInfo); +} + +static void enqueueTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, + int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, + int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, + int z, qint64 uid, Qt::KeyboardModifiers modifiers) +{ + PointerTabletEventInfo eventInfo; + eventInfo.window = window; + eventInfo.local = local; + eventInfo.global = global; + eventInfo.device = device; + eventInfo.pointerType = pointerType; + eventInfo.buttons = buttons; + eventInfo.pressure = pressure; + eventInfo.xTilt = xTilt; + eventInfo.yTilt = yTilt; + eventInfo.tangentialPressure = tangentialPressure; + eventInfo.rotation = rotation; + eventInfo.z = z; + eventInfo.uid = uid; + eventInfo.modifiers = modifiers; + tabletEventQueue.enqueue(eventInfo); +} + +static void flushTouchEvents(QTouchDevice *touchDevice) +{ + while (!touchEventQueue.isEmpty()) { + PointerTouchEventInfo eventInfo = touchEventQueue.dequeue(); + if (eventInfo.window) { + QWindowSystemInterface::handleTouchEvent(eventInfo.window, + touchDevice, + eventInfo.points, + eventInfo.modifiers); + } + } +} + +static void flushTabletEvents() +{ + while (!tabletEventQueue.isEmpty()) { + PointerTabletEventInfo eventInfo = tabletEventQueue.dequeue(); + if (eventInfo.window) { + QWindowSystemInterface::handleTabletEvent(eventInfo.window, + eventInfo.local, + eventInfo.global, + eventInfo.device, + eventInfo.pointerType, + eventInfo.buttons, + eventInfo.pressure, + eventInfo.xTilt, + eventInfo.yTilt, + eventInfo.tangentialPressure, + eventInfo.rotation, + eventInfo.z, + eventInfo.uid, + eventInfo.modifiers); + } + } +} + +static bool draggingActive() +{ +#if QT_CONFIG(draganddrop) + return QWindowsDrag::isDragging(); +#else + return false; +#endif +} + bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) { *result = 0; @@ -171,7 +280,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q return false; } -static void getMouseEventInfo(UINT message, POINTER_BUTTON_CHANGE_TYPE changeType, QPoint globalPos, QEvent::Type *eventType, Qt::MouseButton *mouseButton) +static void getMouseEventInfo(UINT message, POINTER_BUTTON_CHANGE_TYPE changeType, QEvent::Type *eventType, Qt::MouseButton *mouseButton) { static const QHash<POINTER_BUTTON_CHANGE_TYPE, Qt::MouseButton> buttonMapping { {POINTER_CHANGE_FIRSTBUTTON_DOWN, Qt::LeftButton}, @@ -225,28 +334,6 @@ static void getMouseEventInfo(UINT message, POINTER_BUTTON_CHANGE_TYPE changeTyp } *mouseButton = buttonMapping.value(changeType, Qt::NoButton); - - // Pointer messages lack a double click indicator. Check if this is the case here. - if (*eventType == QEvent::MouseButtonPress || - *eventType == QEvent::NonClientAreaMouseButtonPress) { - static LONG lastTime = 0; - static Qt::MouseButton lastButton = Qt::NoButton; - static QEvent::Type lastEvent = QEvent::None; - static QPoint lastPos; - LONG messageTime = GetMessageTime(); - if (*mouseButton == lastButton - && *eventType == lastEvent - && messageTime - lastTime < (LONG)GetDoubleClickTime() - && qAbs(globalPos.x() - lastPos.x()) < GetSystemMetrics(SM_CXDOUBLECLK) - && qAbs(globalPos.y() - lastPos.y()) < GetSystemMetrics(SM_CYDOUBLECLK)) { - *eventType = nonClient ? QEvent::NonClientAreaMouseButtonDblClick : - QEvent::MouseButtonDblClick; - } - lastTime = messageTime; - lastButton = *mouseButton; - lastEvent = *eventType; - lastPos = globalPos; - } } static QWindow *getWindowUnderPointer(QWindow *window, QPoint globalPos) @@ -358,7 +445,7 @@ bool QWindowsPointerHandler::translateMouseTouchPadEvent(QWindow *window, HWND h QEvent::Type eventType; Qt::MouseButton button; - getMouseEventInfo(msg.message, pointerInfo->ButtonChangeType, globalPos, &eventType, &button); + getMouseEventInfo(msg.message, pointerInfo->ButtonChangeType, &eventType, &button); if (et & QtWindows::NonClientEventFlag) { QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, mouseButtons, button, eventType, @@ -426,6 +513,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, if (et & QtWindows::NonClientEventFlag) return false; // Let DefWindowProc() handle Non Client messages. + if (draggingActive()) + return false; // Let DoDragDrop() loop handle it. + if (count < 1) return false; @@ -452,6 +542,8 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, QList<QWindowSystemInterface::TouchPoint> touchPoints; + bool primaryPointer = false; + if (QWindowsContext::verbose > 1) qCDebug(lcQpaEvents).noquote().nospace() << showbase << __FUNCTION__ @@ -494,16 +586,23 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, touchPoint.state = stationaryTouchPoint ? Qt::TouchPointStationary : Qt::TouchPointMoved; m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition); } + if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) + primaryPointer = true; + touchPoints.append(touchPoint); // Avoid getting repeated messages for this frame if there are multiple pointerIds QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId); } - - QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints, - QWindowsKeyMapper::queryKeyboardModifiers()); - - return true; + if (primaryPointer) { + // Postpone event delivery to avoid hanging inside DoDragDrop(). + // Only the primary pointer will generate mouse messages. + enqueueTouchEvent(window, touchPoints, QWindowsKeyMapper::queryKeyboardModifiers()); + } else { + QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints, + QWindowsKeyMapper::queryKeyboardModifiers()); + } + return false; // Allow mouse messages to be generated. } bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, @@ -512,6 +611,9 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin if (et & QtWindows::NonClientEventFlag) return false; // Let DefWindowProc() handle Non Client messages. + if (draggingActive()) + return false; // Let DoDragDrop() loop handle it. + POINTER_PEN_INFO *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo); RECT pRect, dRect; @@ -592,20 +694,25 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin } const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); - QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, - pressure, xTilt, yTilt, tangentialPressure, rotation, z, - pointerId, keyModifiers); - break; + // Postpone event delivery to avoid hanging inside DoDragDrop(). + enqueueTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + pointerId, keyModifiers); + return false; // Allow mouse messages to be generated. } } return true; } -// SetCursorPos()/TrackMouseEvent() will generate old-style WM_MOUSE messages. Handle them here. +// Process old-style mouse messages here. bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result) { Q_UNUSED(et); + // Generate enqueued events. + flushTouchEvents(m_touchDevice); + flushTabletEvents(); + *result = 0; if (msg.message != WM_MOUSELEAVE && msg.message != WM_MOUSEMOVE) return false; diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index d01a7b0a8a..4b70e915a8 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -507,9 +507,7 @@ void QWindowsTheme::refreshFonts() if (!QGuiApplication::desktopSettingsAware()) return; NONCLIENTMETRICS ncm; - ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT); - SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize , &ncm, 0); - + QWindowsContext::nonClientMetrics(&ncm); const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont); const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont); const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont); diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 9c31409644..f340f16679 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -2416,6 +2416,13 @@ void QWindowsWindow::setFrameStrutEventsEnabled(bool enabled) } } +static int getBorderWidth(const QPlatformScreen *screen) +{ + NONCLIENTMETRICS ncm; + QWindowsContext::nonClientMetricsForScreen(&ncm, screen); + return ncm.iBorderWidth + ncm.iPaddedBorderWidth + 2; +} + void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const { // We don't apply the min/max size hint as we change the dpi, because we did not adjust the @@ -2425,10 +2432,11 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const hint.applyToMinMaxInfo(m_data.hwnd, mmi); } - if ((testFlag(WithinMaximize) || (window()->windowStates() & Qt::WindowMinimized)) - && (m_data.flags & Qt::FramelessWindowHint)) { - // This block fixes QTBUG-8361: Frameless windows shouldn't cover the - // taskbar when maximized + // This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the + // taskbar when maximized + if ((testFlag(WithinMaximize) || window()->windowStates().testFlag(Qt::WindowMinimized)) + && (m_data.flags.testFlag(Qt::FramelessWindowHint) + || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))) { const QScreen *screen = window()->screen(); // Documentation of MINMAXINFO states that it will only work for the primary screen @@ -2442,6 +2450,14 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const // If you have the taskbar on top, or on the left you don't want it at (0,0): mmi->ptMaxPosition.x = availableGeometry.x(); mmi->ptMaxPosition.y = availableGeometry.y(); + if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) { + const int borderWidth = getBorderWidth(screen->handle()); + mmi->ptMaxSize.x += borderWidth * 2; + mmi->ptMaxSize.y += borderWidth * 2; + mmi->ptMaxTrackSize = mmi->ptMaxSize; + mmi->ptMaxPosition.x -= borderWidth; + mmi->ptMaxPosition.y -= borderWidth; + } } else if (!screen){ qWarning("window()->screen() returned a null screen"); } diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp index 8851ea59e5..ed482e5dae 100644 --- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp @@ -62,8 +62,10 @@ QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window) : QPlatformBackingStore(window) , m_translucentBackground(false) { - if (QXcbWindow *w = static_cast<QXcbWindow *>(window->handle())) - m_translucentBackground = w->connection()->hasXRender() && QImage::toPixelFormat(w->imageFormat()).alphaSize() > 0; + if (QXcbWindow *w = static_cast<QXcbWindow *>(window->handle())) { + m_translucentBackground = w->connection()->hasXRender() && + QImage::toPixelFormat(w->imageFormat()).alphaUsage() == QPixelFormat::UsesAlpha; + } } QXcbNativeBackingStore::~QXcbNativeBackingStore() diff --git a/src/plugins/platforms/xcb/qxcbatom.cpp b/src/plugins/platforms/xcb/qxcbatom.cpp new file mode 100644 index 0000000000..ecb73cb90b --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbatom.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** 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 "qxcbatom.h" + +#include <QtCore/qglobal.h> + +#include <string.h> + +#include <algorithm> + +static const char *xcb_atomnames = { + // window-manager <-> client protocols + "WM_PROTOCOLS\0" + "WM_DELETE_WINDOW\0" + "WM_TAKE_FOCUS\0" + "_NET_WM_PING\0" + "_NET_WM_CONTEXT_HELP\0" + "_NET_WM_SYNC_REQUEST\0" + "_NET_WM_SYNC_REQUEST_COUNTER\0" + "MANAGER\0" + "_NET_SYSTEM_TRAY_OPCODE\0" + + // ICCCM window state + "WM_STATE\0" + "WM_CHANGE_STATE\0" + "WM_CLASS\0" + "WM_NAME\0" + + // Session management + "WM_CLIENT_LEADER\0" + "WM_WINDOW_ROLE\0" + "SM_CLIENT_ID\0" + "WM_CLIENT_MACHINE\0" + + // Clipboard + "CLIPBOARD\0" + "INCR\0" + "TARGETS\0" + "MULTIPLE\0" + "TIMESTAMP\0" + "SAVE_TARGETS\0" + "CLIP_TEMPORARY\0" + "_QT_SELECTION\0" + "_QT_CLIPBOARD_SENTINEL\0" + "_QT_SELECTION_SENTINEL\0" + "CLIPBOARD_MANAGER\0" + + "RESOURCE_MANAGER\0" + + "_XSETROOT_ID\0" + + "_QT_SCROLL_DONE\0" + "_QT_INPUT_ENCODING\0" + + "_QT_CLOSE_CONNECTION\0" + + "_MOTIF_WM_HINTS\0" + + "DTWM_IS_RUNNING\0" + "ENLIGHTENMENT_DESKTOP\0" + "_DT_SAVE_MODE\0" + "_SGI_DESKS_MANAGER\0" + + // EWMH (aka NETWM) + "_NET_SUPPORTED\0" + "_NET_VIRTUAL_ROOTS\0" + "_NET_WORKAREA\0" + + "_NET_MOVERESIZE_WINDOW\0" + "_NET_WM_MOVERESIZE\0" + + "_NET_WM_NAME\0" + "_NET_WM_ICON_NAME\0" + "_NET_WM_ICON\0" + + "_NET_WM_PID\0" + + "_NET_WM_WINDOW_OPACITY\0" + + "_NET_WM_STATE\0" + "_NET_WM_STATE_ABOVE\0" + "_NET_WM_STATE_BELOW\0" + "_NET_WM_STATE_FULLSCREEN\0" + "_NET_WM_STATE_MAXIMIZED_HORZ\0" + "_NET_WM_STATE_MAXIMIZED_VERT\0" + "_NET_WM_STATE_MODAL\0" + "_NET_WM_STATE_STAYS_ON_TOP\0" + "_NET_WM_STATE_DEMANDS_ATTENTION\0" + + "_NET_WM_USER_TIME\0" + "_NET_WM_USER_TIME_WINDOW\0" + "_NET_WM_FULL_PLACEMENT\0" + + "_NET_WM_WINDOW_TYPE\0" + "_NET_WM_WINDOW_TYPE_DESKTOP\0" + "_NET_WM_WINDOW_TYPE_DOCK\0" + "_NET_WM_WINDOW_TYPE_TOOLBAR\0" + "_NET_WM_WINDOW_TYPE_MENU\0" + "_NET_WM_WINDOW_TYPE_UTILITY\0" + "_NET_WM_WINDOW_TYPE_SPLASH\0" + "_NET_WM_WINDOW_TYPE_DIALOG\0" + "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0" + "_NET_WM_WINDOW_TYPE_POPUP_MENU\0" + "_NET_WM_WINDOW_TYPE_TOOLTIP\0" + "_NET_WM_WINDOW_TYPE_NOTIFICATION\0" + "_NET_WM_WINDOW_TYPE_COMBO\0" + "_NET_WM_WINDOW_TYPE_DND\0" + "_NET_WM_WINDOW_TYPE_NORMAL\0" + "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0" + + "_KDE_NET_WM_FRAME_STRUT\0" + "_NET_FRAME_EXTENTS\0" + + "_NET_STARTUP_INFO\0" + "_NET_STARTUP_INFO_BEGIN\0" + + "_NET_SUPPORTING_WM_CHECK\0" + + "_NET_WM_CM_S0\0" + + "_NET_SYSTEM_TRAY_VISUAL\0" + + "_NET_ACTIVE_WINDOW\0" + + // Property formats + "TEXT\0" + "UTF8_STRING\0" + "CARDINAL\0" + + // xdnd + "XdndEnter\0" + "XdndPosition\0" + "XdndStatus\0" + "XdndLeave\0" + "XdndDrop\0" + "XdndFinished\0" + "XdndTypeList\0" + "XdndActionList\0" + + "XdndSelection\0" + + "XdndAware\0" + "XdndProxy\0" + + "XdndActionCopy\0" + "XdndActionLink\0" + "XdndActionMove\0" + "XdndActionPrivate\0" + + // Xkb + "_XKB_RULES_NAMES\0" + + // XEMBED + "_XEMBED\0" + "_XEMBED_INFO\0" + + // XInput2 + "Button Left\0" + "Button Middle\0" + "Button Right\0" + "Button Wheel Up\0" + "Button Wheel Down\0" + "Button Horiz Wheel Left\0" + "Button Horiz Wheel Right\0" + "Abs MT Position X\0" + "Abs MT Position Y\0" + "Abs MT Touch Major\0" + "Abs MT Touch Minor\0" + "Abs MT Orientation\0" + "Abs MT Pressure\0" + "Abs MT Tracking ID\0" + "Max Contacts\0" + "Rel X\0" + "Rel Y\0" + // XInput2 tablet + "Abs X\0" + "Abs Y\0" + "Abs Pressure\0" + "Abs Tilt X\0" + "Abs Tilt Y\0" + "Abs Wheel\0" + "Abs Distance\0" + "Wacom Serial IDs\0" + "INTEGER\0" + "Rel Horiz Wheel\0" + "Rel Vert Wheel\0" + "Rel Horiz Scroll\0" + "Rel Vert Scroll\0" + "_XSETTINGS_SETTINGS\0" + "_COMPIZ_DECOR_PENDING\0" + "_COMPIZ_DECOR_REQUEST\0" + "_COMPIZ_DECOR_DELETE_PIXMAP\0" + "_COMPIZ_TOOLKIT_ACTION\0" + "_GTK_LOAD_ICONTHEMES\0" + "AT_SPI_BUS\0" + "EDID\0" + "EDID_DATA\0" + "XFree86_DDC_EDID1_RAWDATA\0" + // \0\0 terminates loop. +}; + +QXcbAtom::QXcbAtom() +{ +} + +void QXcbAtom::initialize(xcb_connection_t *connection) +{ + initializeAllAtoms(connection); +} + +void QXcbAtom::initializeAllAtoms(xcb_connection_t *connection) { + const char *names[QXcbAtom::NAtoms]; + const char *ptr = xcb_atomnames; + + int i = 0; + while (*ptr) { + names[i++] = ptr; + while (*ptr) + ++ptr; + ++ptr; + } + + Q_ASSERT(i == QXcbAtom::NAtoms); + + xcb_intern_atom_cookie_t cookies[QXcbAtom::NAtoms]; + + Q_ASSERT(i == QXcbAtom::NAtoms); + for (i = 0; i < QXcbAtom::NAtoms; ++i) + cookies[i] = xcb_intern_atom(connection, false, strlen(names[i]), names[i]); + + for (i = 0; i < QXcbAtom::NAtoms; ++i) { + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookies[i], 0); + m_allAtoms[i] = reply->atom; + free(reply); + } +} + +QXcbAtom::Atom QXcbAtom::qatom(xcb_atom_t xatom) const +{ + return static_cast<QXcbAtom::Atom>(std::find(m_allAtoms, m_allAtoms + QXcbAtom::NAtoms, xatom) - m_allAtoms); +} diff --git a/src/plugins/platforms/xcb/qxcbatom.h b/src/plugins/platforms/xcb/qxcbatom.h new file mode 100644 index 0000000000..233d2eadb7 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbatom.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** 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 QXCBATOM_H +#define QXCBATOM_H + +#include <xcb/xcb.h> + +class QXcbAtom +{ +public: + enum Atom { + // window-manager <-> client protocols + WM_PROTOCOLS, + WM_DELETE_WINDOW, + WM_TAKE_FOCUS, + _NET_WM_PING, + _NET_WM_CONTEXT_HELP, + _NET_WM_SYNC_REQUEST, + _NET_WM_SYNC_REQUEST_COUNTER, + MANAGER, // System tray notification + _NET_SYSTEM_TRAY_OPCODE, // System tray operation + + // ICCCM window state + WM_STATE, + WM_CHANGE_STATE, + WM_CLASS, + WM_NAME, + + // Session management + WM_CLIENT_LEADER, + WM_WINDOW_ROLE, + SM_CLIENT_ID, + WM_CLIENT_MACHINE, + + // Clipboard + CLIPBOARD, + INCR, + TARGETS, + MULTIPLE, + TIMESTAMP, + SAVE_TARGETS, + CLIP_TEMPORARY, + _QT_SELECTION, + _QT_CLIPBOARD_SENTINEL, + _QT_SELECTION_SENTINEL, + CLIPBOARD_MANAGER, + + RESOURCE_MANAGER, + + _XSETROOT_ID, + + _QT_SCROLL_DONE, + _QT_INPUT_ENCODING, + + // Qt/XCB specific + _QT_CLOSE_CONNECTION, + + _MOTIF_WM_HINTS, + + DTWM_IS_RUNNING, + ENLIGHTENMENT_DESKTOP, + _DT_SAVE_MODE, + _SGI_DESKS_MANAGER, + + // EWMH (aka NETWM) + _NET_SUPPORTED, + _NET_VIRTUAL_ROOTS, + _NET_WORKAREA, + + _NET_MOVERESIZE_WINDOW, + _NET_WM_MOVERESIZE, + + _NET_WM_NAME, + _NET_WM_ICON_NAME, + _NET_WM_ICON, + + _NET_WM_PID, + + _NET_WM_WINDOW_OPACITY, + + _NET_WM_STATE, + _NET_WM_STATE_ABOVE, + _NET_WM_STATE_BELOW, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_MAXIMIZED_VERT, + _NET_WM_STATE_MODAL, + _NET_WM_STATE_STAYS_ON_TOP, + _NET_WM_STATE_DEMANDS_ATTENTION, + + _NET_WM_USER_TIME, + _NET_WM_USER_TIME_WINDOW, + _NET_WM_FULL_PLACEMENT, + + _NET_WM_WINDOW_TYPE, + _NET_WM_WINDOW_TYPE_DESKTOP, + _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_WINDOW_TYPE_TOOLBAR, + _NET_WM_WINDOW_TYPE_MENU, + _NET_WM_WINDOW_TYPE_UTILITY, + _NET_WM_WINDOW_TYPE_SPLASH, + _NET_WM_WINDOW_TYPE_DIALOG, + _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + _NET_WM_WINDOW_TYPE_POPUP_MENU, + _NET_WM_WINDOW_TYPE_TOOLTIP, + _NET_WM_WINDOW_TYPE_NOTIFICATION, + _NET_WM_WINDOW_TYPE_COMBO, + _NET_WM_WINDOW_TYPE_DND, + _NET_WM_WINDOW_TYPE_NORMAL, + _KDE_NET_WM_WINDOW_TYPE_OVERRIDE, + + _KDE_NET_WM_FRAME_STRUT, + _NET_FRAME_EXTENTS, + + _NET_STARTUP_INFO, + _NET_STARTUP_INFO_BEGIN, + + _NET_SUPPORTING_WM_CHECK, + + _NET_WM_CM_S0, + + _NET_SYSTEM_TRAY_VISUAL, + + _NET_ACTIVE_WINDOW, + + // Property formats + TEXT, + UTF8_STRING, + CARDINAL, + + // Xdnd + XdndEnter, + XdndPosition, + XdndStatus, + XdndLeave, + XdndDrop, + XdndFinished, + XdndTypelist, + XdndActionList, + + XdndSelection, + + XdndAware, + XdndProxy, + + XdndActionCopy, + XdndActionLink, + XdndActionMove, + XdndActionPrivate, + + // Xkb + _XKB_RULES_NAMES, + + // XEMBED + _XEMBED, + _XEMBED_INFO, + + // XInput2 + ButtonLeft, + ButtonMiddle, + ButtonRight, + ButtonWheelUp, + ButtonWheelDown, + ButtonHorizWheelLeft, + ButtonHorizWheelRight, + AbsMTPositionX, + AbsMTPositionY, + AbsMTTouchMajor, + AbsMTTouchMinor, + AbsMTOrientation, + AbsMTPressure, + AbsMTTrackingID, + MaxContacts, + RelX, + RelY, + // XInput2 tablet + AbsX, + AbsY, + AbsPressure, + AbsTiltX, + AbsTiltY, + AbsWheel, + AbsDistance, + WacomSerialIDs, + INTEGER, + RelHorizWheel, + RelVertWheel, + RelHorizScroll, + RelVertScroll, + + _XSETTINGS_SETTINGS, + + _COMPIZ_DECOR_PENDING, + _COMPIZ_DECOR_REQUEST, + _COMPIZ_DECOR_DELETE_PIXMAP, + _COMPIZ_TOOLKIT_ACTION, + _GTK_LOAD_ICONTHEMES, + + AT_SPI_BUS, + + EDID, + EDID_DATA, + XFree86_DDC_EDID1_RAWDATA, + + NAtoms + }; + + QXcbAtom(); + void initialize(xcb_connection_t *connection); + + inline xcb_atom_t atom(QXcbAtom::Atom atom) const { return m_allAtoms[atom]; } + QXcbAtom::Atom qatom(xcb_atom_t atom) const; + +protected: + void initializeAllAtoms(xcb_connection_t *connection); + +private: + xcb_atom_t m_allAtoms[QXcbAtom::NAtoms]; +}; + +#endif // QXCBATOM_H diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp index c29eb29b7e..c8c806749f 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp +++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp @@ -106,7 +106,7 @@ public: void put(xcb_drawable_t dst, const QRegion ®ion, const QPoint &offset); void preparePaint(const QRegion ®ion); - static bool createSystemVShmSegment(QXcbConnection *c, size_t segmentSize = 1, + static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize = 1, xcb_shm_segment_info_t *shm_info = nullptr); private: @@ -406,12 +406,12 @@ void QXcbBackingStoreImage::createShmSegment(size_t segmentSize) } else #endif { - if (createSystemVShmSegment(connection(), segmentSize, &m_shm_info)) + if (createSystemVShmSegment(xcb_connection(), segmentSize, &m_shm_info)) m_segmentSize = segmentSize; } } -bool QXcbBackingStoreImage::createSystemVShmSegment(QXcbConnection *c, size_t segmentSize, +bool QXcbBackingStoreImage::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, xcb_shm_segment_info_t *shmInfo) { const int id = shmget(IPC_PRIVATE, segmentSize, IPC_CREAT | 0600); @@ -429,17 +429,17 @@ bool QXcbBackingStoreImage::createSystemVShmSegment(QXcbConnection *c, size_t se if (shmctl(id, IPC_RMID, 0) == -1) qCWarning(lcQpaXcb, "Error while marking the shared memory segment to be destroyed"); - const auto seg = xcb_generate_id(c->xcb_connection()); - auto cookie = xcb_shm_attach_checked(c->xcb_connection(), seg, id, false); - auto *error = xcb_request_check(c->xcb_connection(), cookie); + const auto seg = xcb_generate_id(c); + auto cookie = xcb_shm_attach_checked(c, seg, id, false); + auto *error = xcb_request_check(c, cookie); if (error) { - c->printXcbError("xcb_shm_attach() failed with error", error); + qCWarning(lcQpaXcb(), "xcb_shm_attach() failed"); free(error); if (shmdt(addr) == -1) qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p", errno, strerror(errno), addr); return false; } else if (!shmInfo) { // this was a test run, free the allocated test segment - xcb_shm_detach(c->xcb_connection(), seg); + xcb_shm_detach(c, seg); auto shmaddr = static_cast<quint8 *>(addr); if (shmdt(shmaddr) == -1) qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p", errno, strerror(errno), shmaddr); @@ -766,7 +766,7 @@ void QXcbBackingStoreImage::preparePaint(const QRegion ®ion) m_pendingFlush |= region; } -bool QXcbBackingStore::createSystemVShmSegment(QXcbConnection *c, size_t segmentSize, void *shmInfo) +bool QXcbBackingStore::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, void *shmInfo) { auto info = reinterpret_cast<xcb_shm_segment_info_t *>(shmInfo); return QXcbBackingStoreImage::createSystemVShmSegment(c, segmentSize, info); diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.h b/src/plugins/platforms/xcb/qxcbbackingstore.h index 39d023cb9d..b91e5c7dc2 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.h +++ b/src/plugins/platforms/xcb/qxcbbackingstore.h @@ -74,7 +74,7 @@ public: void beginPaint(const QRegion &) override; void endPaint() override; - static bool createSystemVShmSegment(QXcbConnection *c, size_t segmentSize = 1, + static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize = 1, void *shmInfo = nullptr); protected: diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp index 84831cdbe5..3fd14a659e 100644 --- a/src/plugins/platforms/xcb/qxcbclipboard.cpp +++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp @@ -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; diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index 4e24c970b4..9e857ea2ff 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -38,12 +38,10 @@ ****************************************************************************/ #include <QtGui/private/qguiapplication_p.h> -#include <QtGui/private/qhighdpiscaling_p.h> #include <QtCore/QDebug> #include "qxcbconnection.h" #include "qxcbkeyboard.h" -#include "qxcbscreen.h" #include "qxcbwindow.h" #include "qxcbclipboard.h" #if QT_CONFIG(draganddrop) @@ -57,39 +55,25 @@ #include "qxcbglintegration.h" #include "qxcbcursor.h" #include "qxcbbackingstore.h" +#include "qxcbeventqueue.h" -#include <QSocketNotifier> #include <QAbstractEventDispatcher> -#include <QTimer> #include <QByteArray> #include <QScopedPointer> -#include <algorithm> - #include <stdio.h> #include <errno.h> -#include <xcb/shm.h> -#include <xcb/sync.h> + #include <xcb/xfixes.h> -#include <xcb/xinerama.h> - -#if QT_CONFIG(xcb_xlib) -#define register /* C++17 deprecated register */ -#include <X11/Xlib.h> -#include <X11/Xlib-xcb.h> -#include <X11/Xlibint.h> -#include <X11/Xutil.h> -#undef register +#if QT_CONFIG(xkb) +#define explicit dont_use_cxx_explicit +#include <xcb/xkb.h> +#undef explicit #endif - #if QT_CONFIG(xcb_xinput) #include <xcb/xinput.h> #endif -#if QT_CONFIG(xcb_render) -#include <xcb/render.h> -#endif - QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input") @@ -97,7 +81,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(lcQpaXcb, "qt.qpa.xcb") // for general (uncategorized) XCB logging +Q_LOGGING_CATEGORY(lcQpaEventReader, "qt.qpa.events.reader") Q_LOGGING_CATEGORY(lcQpaPeeker, "qt.qpa.peeker") Q_LOGGING_CATEGORY(lcQpaKeyboard, "qt.qpa.xkeyboard") Q_LOGGING_CATEGORY(lcQpaXDnd, "qt.qpa.xdnd") @@ -108,519 +92,27 @@ Q_LOGGING_CATEGORY(lcQpaXDnd, "qt.qpa.xdnd") #define XCB_GE_GENERIC 35 #endif -#if QT_CONFIG(xcb_xinput) -// Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed: -// - "pad0" became "extension" -// - "pad1" and "pad" became "pad0" -// New and old version of this struct share the following fields: -typedef struct qt_xcb_ge_event_t { - uint8_t response_type; - uint8_t extension; - uint16_t sequence; - uint32_t length; - uint16_t event_type; -} qt_xcb_ge_event_t; - -static inline bool isXIEvent(xcb_generic_event_t *event, int opCode) -{ - qt_xcb_ge_event_t *e = reinterpret_cast<qt_xcb_ge_event_t *>(event); - return e->extension == opCode; -} -#endif // QT_CONFIG(xcb_xinput) - -#if QT_CONFIG(xcb_xlib) -static const char * const xcbConnectionErrors[] = { - "No error", /* Error 0 */ - "I/O error", /* XCB_CONN_ERROR */ - "Unsupported extension used", /* XCB_CONN_CLOSED_EXT_NOTSUPPORTED */ - "Out of memory", /* XCB_CONN_CLOSED_MEM_INSUFFICIENT */ - "Maximum allowed requested length exceeded", /* XCB_CONN_CLOSED_REQ_LEN_EXCEED */ - "Failed to parse display string", /* XCB_CONN_CLOSED_PARSE_ERR */ - "No such screen on display", /* XCB_CONN_CLOSED_INVALID_SCREEN */ - "Error during FD passing" /* XCB_CONN_CLOSED_FDPASSING_FAILED */ -}; - -static int nullErrorHandler(Display *dpy, XErrorEvent *err) -{ -#ifndef Q_XCB_DEBUG - Q_UNUSED(dpy); - Q_UNUSED(err); -#else - const int buflen = 1024; - char buf[buflen]; - - XGetErrorText(dpy, err->error_code, buf, buflen); - fprintf(stderr, "X Error: serial %lu error %d %s\n", err->serial, (int) err->error_code, buf); -#endif - return 0; -} - -static int ioErrorHandler(Display *dpy) -{ - xcb_connection_t *conn = XGetXCBConnection(dpy); - if (conn != NULL) { - /* Print a message with a textual description of the error */ - int code = xcb_connection_has_error(conn); - const char *str = "Unknown error"; - int arrayLength = sizeof(xcbConnectionErrors) / sizeof(xcbConnectionErrors[0]); - if (code >= 0 && code < arrayLength) - str = xcbConnectionErrors[code]; - - qWarning("The X11 connection broke: %s (code %d)", str, code); - } - return _XDefaultIOError(dpy); -} -#endif - -QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc) const -{ - for (QXcbScreen *screen : m_screens) { - if (screen->root() == rootWindow && screen->crtc() == crtc) - return screen; - } - - return 0; -} - -QXcbScreen* QXcbConnection::findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output) const -{ - for (QXcbScreen *screen : m_screens) { - if (screen->root() == rootWindow && screen->output() == output) - return screen; - } - - return 0; -} - -QXcbVirtualDesktop* QXcbConnection::virtualDesktopForRootWindow(xcb_window_t rootWindow) const -{ - for (QXcbVirtualDesktop *virtualDesktop : m_virtualDesktops) { - if (virtualDesktop->screen()->root == rootWindow) - return virtualDesktop; - } - - return 0; -} - -/*! - \brief Synchronizes the screen list, adds new screens, removes deleted ones -*/ -void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event) -{ - if (event->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) { - xcb_randr_crtc_change_t crtc = event->u.cc; - QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(crtc.window); - if (!virtualDesktop) - // Not for us - return; - - QXcbScreen *screen = findScreenForCrtc(crtc.window, crtc.crtc); - qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_CRTC_CHANGE:" << crtc.crtc - << "mode" << crtc.mode << "relevant screen" << screen; - // Only update geometry when there's a valid mode on the CRTC - // CRTC with node mode could mean that output has been disabled, and we'll - // get RRNotifyOutputChange notification for that. - if (screen && crtc.mode) { - if (crtc.rotation == XCB_RANDR_ROTATION_ROTATE_90 || - crtc.rotation == XCB_RANDR_ROTATION_ROTATE_270) - std::swap(crtc.width, crtc.height); - screen->updateGeometry(QRect(crtc.x, crtc.y, crtc.width, crtc.height), crtc.rotation); - if (screen->mode() != crtc.mode) - screen->updateRefreshRate(crtc.mode); - } - - } else if (event->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) { - xcb_randr_output_change_t output = event->u.oc; - QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(output.window); - if (!virtualDesktop) - // Not for us - return; - - QXcbScreen *screen = findScreenForOutput(output.window, output.output); - qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_OUTPUT_CHANGE:" << output.output; - - if (screen && output.connection == XCB_RANDR_CONNECTION_DISCONNECTED) { - qCDebug(lcQpaScreen) << "screen" << screen->name() << "has been disconnected"; - destroyScreen(screen); - } else if (!screen && output.connection == XCB_RANDR_CONNECTION_CONNECTED) { - // New XRandR output is available and it's enabled - if (output.crtc != XCB_NONE && output.mode != XCB_NONE) { - auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(), - output.output, output.config_timestamp); - // Find a fake screen - const auto scrs = virtualDesktop->screens(); - for (QPlatformScreen *scr : scrs) { - QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(scr); - if (xcbScreen->output() == XCB_NONE) { - screen = xcbScreen; - break; - } - } - - if (screen) { - QString nameWas = screen->name(); - // Transform the fake screen into a physical screen - screen->setOutput(output.output, outputInfo.get()); - updateScreen(screen, output); - qCDebug(lcQpaScreen) << "output" << screen->name() - << "is connected and enabled; was fake:" << nameWas; - } else { - screen = createScreen(virtualDesktop, output, outputInfo.get()); - qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled"; - } - QHighDpiScaling::updateHighDpiScaling(); - } - } else if (screen) { - if (output.crtc == XCB_NONE && output.mode == XCB_NONE) { - // Screen has been disabled - auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(), - output.output, output.config_timestamp); - if (outputInfo->crtc == XCB_NONE) { - qCDebug(lcQpaScreen) << "output" << screen->name() << "has been disabled"; - destroyScreen(screen); - } else { - qCDebug(lcQpaScreen) << "output" << screen->name() << "has been temporarily disabled for the mode switch"; - // Reset crtc to skip RRCrtcChangeNotify events, - // because they may be invalid in the middle of the mode switch - screen->setCrtc(XCB_NONE); - } - } else { - updateScreen(screen, output); - qCDebug(lcQpaScreen) << "output has changed" << screen; - } - } - - qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name(); - } -} - -bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output) -{ - auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), rootWindow); - if (!primary) - qWarning("failed to get the primary output of the screen"); - - const bool isPrimary = primary ? (primary->output == output) : false; - - return isPrimary; -} - -void QXcbConnection::updateScreen(QXcbScreen *screen, const xcb_randr_output_change_t &outputChange) -{ - screen->setCrtc(outputChange.crtc); // Set the new crtc, because it can be invalid - screen->updateGeometry(outputChange.config_timestamp); - if (screen->mode() != outputChange.mode) - screen->updateRefreshRate(outputChange.mode); - // Only screen which belongs to the primary virtual desktop can be a primary screen - if (screen->screenNumber() == m_primaryScreenNumber) { - if (!screen->isPrimary() && checkOutputIsPrimary(outputChange.window, outputChange.output)) { - screen->setPrimary(true); - - // If the screen became primary, reshuffle the order in QGuiApplicationPrivate - const int idx = m_screens.indexOf(screen); - if (idx > 0) { - qAsConst(m_screens).first()->setPrimary(false); - m_screens.swap(0, idx); - } - screen->virtualDesktop()->setPrimaryScreen(screen); - QXcbIntegration::instance()->setPrimaryScreen(screen); - } - } -} - -QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop, - const xcb_randr_output_change_t &outputChange, - xcb_randr_get_output_info_reply_t *outputInfo) -{ - QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputChange.output, outputInfo); - // Only screen which belongs to the primary virtual desktop can be a primary screen - if (screen->screenNumber() == m_primaryScreenNumber) - screen->setPrimary(checkOutputIsPrimary(outputChange.window, outputChange.output)); - - if (screen->isPrimary()) { - if (!m_screens.isEmpty()) - qAsConst(m_screens).first()->setPrimary(false); - - m_screens.prepend(screen); - } else { - m_screens.append(screen); - } - virtualDesktop->addScreen(screen); - QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary()); - - return screen; -} - -void QXcbConnection::destroyScreen(QXcbScreen *screen) -{ - QXcbVirtualDesktop *virtualDesktop = screen->virtualDesktop(); - if (virtualDesktop->screens().count() == 1) { - // If there are no other screens on the same virtual desktop, - // then transform the physical screen into a fake screen. - const QString nameWas = screen->name(); - screen->setOutput(XCB_NONE, nullptr); - qCDebug(lcQpaScreen) << "transformed" << nameWas << "to fake" << screen; - } else { - // There is more than one screen on the same virtual desktop, remove the screen - m_screens.removeOne(screen); - virtualDesktop->removeScreen(screen); - - // When primary screen is removed, set the new primary screen - // which belongs to the primary virtual desktop. - if (screen->isPrimary()) { - QXcbScreen *newPrimary = static_cast<QXcbScreen *>(virtualDesktop->screens().at(0)); - newPrimary->setPrimary(true); - const int idx = m_screens.indexOf(newPrimary); - if (idx > 0) - m_screens.swap(0, idx); - QXcbIntegration::instance()->setPrimaryScreen(newPrimary); - } - - QXcbIntegration::instance()->destroyScreen(screen); - } -} - -void QXcbConnection::initializeScreens() -{ - xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup); - int xcbScreenNumber = 0; // screen number in the xcb sense - QXcbScreen *primaryScreen = nullptr; - while (it.rem) { - // Each "screen" in xcb terminology is a virtual desktop, - // potentially a collection of separate juxtaposed monitors. - // But we want a separate QScreen for each output (e.g. DVI-I-1, VGA-1, etc.) - // which will become virtual siblings. - xcb_screen_t *xcbScreen = it.data; - QXcbVirtualDesktop *virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber); - m_virtualDesktops.append(virtualDesktop); - QList<QPlatformScreen *> siblings; - if (has_randr_extension) { - // RRGetScreenResourcesCurrent is fast but it may return nothing if the - // configuration is not initialized wrt to the hardware. We should call - // RRGetScreenResources in this case. - auto resources_current = Q_XCB_REPLY(xcb_randr_get_screen_resources_current, - xcb_connection(), xcbScreen->root); - if (!resources_current) { - qWarning("failed to get the current screen resources"); - } else { - xcb_timestamp_t timestamp = 0; - xcb_randr_output_t *outputs = nullptr; - int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources_current.get()); - if (outputCount) { - timestamp = resources_current->config_timestamp; - outputs = xcb_randr_get_screen_resources_current_outputs(resources_current.get()); - } else { - auto resources = Q_XCB_REPLY(xcb_randr_get_screen_resources, - xcb_connection(), xcbScreen->root); - if (!resources) { - qWarning("failed to get the screen resources"); - } else { - timestamp = resources->config_timestamp; - outputCount = xcb_randr_get_screen_resources_outputs_length(resources.get()); - outputs = xcb_randr_get_screen_resources_outputs(resources.get()); - } - } - - if (outputCount) { - auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), xcbScreen->root); - if (!primary) { - qWarning("failed to get the primary output of the screen"); - } else { - for (int i = 0; i < outputCount; i++) { - auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info, - xcb_connection(), outputs[i], timestamp); - // Invalid, disconnected or disabled output - if (!output) - continue; - - if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) { - qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable( - QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()), - xcb_randr_get_output_info_name_length(output.get())))); - continue; - } - - if (output->crtc == XCB_NONE) { - qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable( - QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()), - xcb_randr_get_output_info_name_length(output.get())))); - continue; - } - - QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputs[i], output.get()); - siblings << screen; - m_screens << screen; - - // There can be multiple outputs per screen, use either - // the first or an exact match. An exact match isn't - // always available if primary->output is XCB_NONE - // or currently disconnected output. - if (m_primaryScreenNumber == xcbScreenNumber) { - if (!primaryScreen || (primary && outputs[i] == primary->output)) { - if (primaryScreen) - primaryScreen->setPrimary(false); - primaryScreen = screen; - primaryScreen->setPrimary(true); - siblings.prepend(siblings.takeLast()); - } - } - } - } - } - } - } else if (has_xinerama_extension) { - // Xinerama is available - auto screens = Q_XCB_REPLY(xcb_xinerama_query_screens, m_connection); - if (screens) { - xcb_xinerama_screen_info_iterator_t it = xcb_xinerama_query_screens_screen_info_iterator(screens.get()); - while (it.rem) { - xcb_xinerama_screen_info_t *screen_info = it.data; - QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, - XCB_NONE, nullptr, - screen_info, it.index); - siblings << screen; - m_screens << screen; - xcb_xinerama_screen_info_next(&it); - } - } - } - if (siblings.isEmpty()) { - // If there are no XRandR outputs or XRandR extension is missing, - // then create a fake/legacy screen. - QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, nullptr); - qCDebug(lcQpaScreen) << "created fake screen" << screen; - m_screens << screen; - if (m_primaryScreenNumber == xcbScreenNumber) { - primaryScreen = screen; - primaryScreen->setPrimary(true); - } - siblings << screen; - } - virtualDesktop->setScreens(siblings); - xcb_screen_next(&it); - ++xcbScreenNumber; - } // for each xcb screen - - for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops)) - virtualDesktop->subscribeToXFixesSelectionNotify(); - - if (m_virtualDesktops.isEmpty()) { - qFatal("QXcbConnection: no screens available"); - } else { - // Ensure the primary screen is first on the list - if (primaryScreen) { - if (qAsConst(m_screens).first() != primaryScreen) { - m_screens.removeOne(primaryScreen); - m_screens.prepend(primaryScreen); - } - } - - // Push the screens to QGuiApplication - for (QXcbScreen *screen : qAsConst(m_screens)) { - qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")"; - QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary()); - } - - qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name(); - } -} - -QXcbConnection *QXcbConnection::create(QXcbNativeInterface *nativeInterface, bool canGrabServer, - xcb_visualid_t defaultVisualId, - const char *displayNameIn) -{ - const QByteArray displayName = displayNameIn ? QByteArray(displayNameIn) : qgetenv("DISPLAY"); - int primaryScreenNumber = 0; - void *xlibDisplay = nullptr; - xcb_connection_t *connection = nullptr; -#if QT_CONFIG(xcb_xlib) - Display *dpy = XOpenDisplay(displayName.constData()); - if (dpy) { - primaryScreenNumber = DefaultScreen(dpy); - connection = XGetXCBConnection(dpy); - XSetEventQueueOwner(dpy, XCBOwnsEventQueue); - XSetErrorHandler(nullErrorHandler); - XSetIOErrorHandler(ioErrorHandler); - xlibDisplay = dpy; - } -#else - connection = xcb_connect(displayName.constData(), &primaryScreenNumber); -#endif // QT_CONFIG(xcb_xlib) - if (Q_UNLIKELY(connection == nullptr)) { - qWarning("QXcbConnection: Could not connect to display \"%s\"", displayName.constData()); - return nullptr; - } - if (Q_UNLIKELY(xcb_connection_has_error(connection))) { -#if QT_CONFIG(xcb_xlib) - XCloseDisplay(static_cast<Display *>(xlibDisplay)); -#else - xcb_disconnect(connection); -#endif - qWarning("QXcbConnection: Errors occurred connecting to display \"%s\"", displayName.constData()); - return nullptr; - } - return new QXcbConnection(connection, primaryScreenNumber, nativeInterface, - canGrabServer, defaultVisualId, displayName, xlibDisplay); -} - - -QXcbConnection::QXcbConnection(xcb_connection_t *c, int primaryScreenNumber, - QXcbNativeInterface *nativeInterface, bool canGrabServer, - xcb_visualid_t defaultVisualId, const QByteArray &displayName, - void *xlibDisplay) - : m_connection(c) +QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, xcb_visualid_t defaultVisualId, const char *displayName) + : QXcbBasicConnection(displayName) , m_canGrabServer(canGrabServer) , m_defaultVisualId(defaultVisualId) - , m_primaryScreenNumber(primaryScreenNumber) - , m_displayName(displayName) , m_nativeInterface(nativeInterface) -#if QT_CONFIG(xcb_xlib) - , m_xlib_display(xlibDisplay) -#endif { - m_reader = new QXcbEventReader(this); - m_reader->start(); - - xcb_extension_t *extensions[] = { - &xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id, -#if QT_CONFIG(xkb) - &xcb_xkb_id, -#endif -#if QT_CONFIG(xcb_render) - &xcb_render_id, -#endif -#if QT_CONFIG(xcb_xinput) - &xcb_input_id, -#endif - 0 - }; - - for (xcb_extension_t **ext_it = extensions; *ext_it; ++ext_it) - xcb_prefetch_extension_data (m_connection, *ext_it); + if (!isConnected()) + return; - m_setup = xcb_get_setup(xcb_connection()); + m_eventQueue = new QXcbEventQueue(this); m_xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toLower(); - initializeAllAtoms(); - - initializeXSync(); - if (!qEnvironmentVariableIsSet("QT_XCB_NO_MITSHM")) - initializeShm(); - if (!qEnvironmentVariableIsSet("QT_XCB_NO_XRANDR")) - initializeXRandr(); - if (!has_randr_extension) - initializeXinerama(); - initializeXFixes(); initializeScreens(); - initializeXRender(); #if QT_CONFIG(xcb_xinput) - if (!qEnvironmentVariableIsSet("QT_XCB_NO_XI2")) - initializeXInput2(); + if (hasXInput2()) { + xi2SetupDevices(); + xi2SelectStateEvents(); + } #endif - initializeXShape(); - initializeXKB(); m_wmSupport.reset(new QXcbWMSupport(this)); m_keyboard = new QXcbKeyboard(this); @@ -646,12 +138,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 @@ -663,22 +151,9 @@ QXcbConnection::~QXcbConnection() delete m_glIntegration; - if (isConnected()) { -#if QT_CONFIG(xcb_xlib) - XCloseDisplay(static_cast<Display *>(m_xlib_display)); -#else - xcb_disconnect(xcb_connection()); -#endif - } - delete m_keyboard; } -bool QXcbConnection::isConnected() const -{ - return m_connection && !xcb_connection_has_error(m_connection); -} - QXcbScreen *QXcbConnection::primaryScreen() const { if (!m_screens.isEmpty()) { @@ -783,17 +258,17 @@ void QXcbConnection::printXcbEvent(const QLoggingCategory &log, const char *mess CASE_PRINT_AND_RETURN( XCB_GE_GENERIC ); } // XFixes - if (has_xfixes && response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) + if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY)) PRINT_AND_RETURN("XCB_XFIXES_SELECTION_NOTIFY"); + // XRandR - if (has_randr_extension) { - if (response_type == xrandr_first_event + XCB_RANDR_NOTIFY) - PRINT_AND_RETURN("XCB_RANDR_NOTIFY"); - if (response_type == xrandr_first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) - PRINT_AND_RETURN("XCB_RANDR_SCREEN_CHANGE_NOTIFY"); - } + if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) + PRINT_AND_RETURN("XCB_RANDR_NOTIFY"); + if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) + PRINT_AND_RETURN("XCB_RANDR_SCREEN_CHANGE_NOTIFY"); + // XKB - if (response_type == xkb_first_event) + if (isXkbType(response_type)) PRINT_AND_RETURN("XCB_XKB_* event"); // UNKNOWN @@ -1051,11 +526,10 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) printXcbEvent(lcQpaEvents(), "Event", event); long result = 0; // Used only by MS Windows - QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(); - bool handledByNativeEventFilter = dispatcher && dispatcher->filterNativeEvent( - m_nativeInterface->nativeEventType(), event, &result); - if (handledByNativeEventFilter) - return; + if (QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance()) { + if (dispatcher->filterNativeEvent(m_nativeInterface->nativeEventType(), event, &result)) + return; + } uint response_type = event->response_type & ~0x80; @@ -1187,7 +661,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) #if QT_CONFIG(xcb_xinput) case XCB_GE_GENERIC: // Here the windowEventListener is invoked from xi2HandleEvent() - if (hasXInput2() && isXIEvent(event, m_xiOpCode)) + if (hasXInput2() && isXIEvent(event)) xi2HandleEvent(reinterpret_cast<xcb_ge_event_t *>(event)); break; #endif @@ -1200,7 +674,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) return; handled = true; - if (has_xfixes && response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) { + if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY)) { auto notify_event = reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event); setTime(notify_event->timestamp); #ifndef QT_NO_CLIPBOARD @@ -1208,14 +682,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) #endif for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops)) virtualDesktop->handleXFixesSelectionNotify(notify_event); - } else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_NOTIFY) { + } else if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) { updateScreens(reinterpret_cast<xcb_randr_notify_event_t *>(event)); - } else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { + } else if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) { auto change_event = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(event); if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root)) virtualDesktop->handleScreenChange(change_event); #if QT_CONFIG(xkb) - } else if (response_type == xkb_first_event) { // https://bugs.freedesktop.org/show_bug.cgi?id=51295 + } else if (isXkbType(response_type)) { auto xkb_event = reinterpret_cast<_xkb_event *>(event); if (xkb_event->any.deviceID == m_keyboard->coreDeviceId()) { switch (xkb_event->any.xkbType) { @@ -1254,151 +728,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; @@ -1416,38 +745,13 @@ void QXcbConnection::setMousePressWindow(QXcbWindow *w) void QXcbConnection::grabServer() { if (m_canGrabServer) - xcb_grab_server(m_connection); + xcb_grab_server(xcb_connection()); } void QXcbConnection::ungrabServer() { if (m_canGrabServer) - 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_ungrab_server(xcb_connection()); } xcb_timestamp_t QXcbConnection::getTimestamp() @@ -1461,12 +765,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); @@ -1564,21 +866,6 @@ xcb_window_t QXcbConnection::clientLeader() return m_clientLeader; } -#if QT_CONFIG(xcb_xinput) -static inline bool isXIType(xcb_generic_event_t *event, int opCode, uint16_t type) -{ - if (!isXIEvent(event, opCode)) - return false; - - auto *e = reinterpret_cast<qt_xcb_ge_event_t *>(event); - return e->event_type == type; -} -#endif -static inline bool isValid(xcb_generic_event_t *event) -{ - return event && (event->response_type & ~0x80); -} - /*! \internal Compresses events of the same type to avoid swamping the event queue. @@ -1591,22 +878,21 @@ 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 { + if (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) + return false; + 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) { @@ -1614,58 +900,93 @@ bool QXcbConnection::compressEvent(xcb_generic_event_t *event, int currentIndex, return false; // compress XI_Motion - if (isXIType(event, m_xiOpCode, XCB_INPUT_MOTION)) { + if (isXIType(event, 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, 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); + if (isXIType(event, XCB_INPUT_TOUCH_UPDATE)) { + 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, 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; } -void QXcbConnection::processXcbEvents() +bool QXcbConnection::isUserInputEvent(xcb_generic_event_t *event) const +{ + auto eventType = event->response_type & ~0x80; + bool isInputEvent = eventType == XCB_BUTTON_PRESS || + eventType == XCB_BUTTON_RELEASE || + eventType == XCB_KEY_PRESS || + eventType == XCB_KEY_RELEASE || + eventType == XCB_MOTION_NOTIFY || + eventType == XCB_ENTER_NOTIFY || + eventType == XCB_LEAVE_NOTIFY; + if (isInputEvent) + return true; + +#if QT_CONFIG(xcb_xinput) + if (connection()->hasXInput2()) { + isInputEvent = isXIType(event, XCB_INPUT_BUTTON_PRESS) || + isXIType(event, XCB_INPUT_BUTTON_RELEASE) || + isXIType(event, XCB_INPUT_MOTION) || + isXIType(event, XCB_INPUT_TOUCH_BEGIN) || + isXIType(event, XCB_INPUT_TOUCH_UPDATE) || + isXIType(event, XCB_INPUT_TOUCH_END) || + isXIType(event, XCB_INPUT_ENTER) || + isXIType(event, XCB_INPUT_LEAVE) || + // wacom driver's way of reporting tool proximity + isXIType(event, XCB_INPUT_PROPERTY); + } + if (isInputEvent) + return true; +#endif + + if (eventType == XCB_CLIENT_MESSAGE) { + auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event); + if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::WM_PROTOCOLS)) + if (clientMessage->data.data32[0] == atom(QXcbAtom::WM_DELETE_WINDOW)) + isInputEvent = true; + } + + return isInputEvent; +} + +void QXcbConnection::processXcbEvents(QEventLoop::ProcessEventsFlags flags) { int connection_error = xcb_connection_has_error(xcb_connection()); if (connection_error) { @@ -1673,22 +994,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(flags)) { 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)) + if (compressEvent(event)) continue; #ifndef QT_NO_CLIPBOARD @@ -1707,285 +1023,28 @@ 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(); - } - eventqueue->clear(); - - m_reader->unlock(); + handleXcbEvent(event); - m_peekerIndexCacheDirty = m_mainEventLoopFlushedQueue = true; + // 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(); + } // 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()); } -static const char * xcb_atomnames = { - // window-manager <-> client protocols - "WM_PROTOCOLS\0" - "WM_DELETE_WINDOW\0" - "WM_TAKE_FOCUS\0" - "_NET_WM_PING\0" - "_NET_WM_CONTEXT_HELP\0" - "_NET_WM_SYNC_REQUEST\0" - "_NET_WM_SYNC_REQUEST_COUNTER\0" - "MANAGER\0" - "_NET_SYSTEM_TRAY_OPCODE\0" - - // ICCCM window state - "WM_STATE\0" - "WM_CHANGE_STATE\0" - "WM_CLASS\0" - "WM_NAME\0" - - // Session management - "WM_CLIENT_LEADER\0" - "WM_WINDOW_ROLE\0" - "SM_CLIENT_ID\0" - "WM_CLIENT_MACHINE\0" - - // Clipboard - "CLIPBOARD\0" - "INCR\0" - "TARGETS\0" - "MULTIPLE\0" - "TIMESTAMP\0" - "SAVE_TARGETS\0" - "CLIP_TEMPORARY\0" - "_QT_SELECTION\0" - "_QT_CLIPBOARD_SENTINEL\0" - "_QT_SELECTION_SENTINEL\0" - "CLIPBOARD_MANAGER\0" - - "RESOURCE_MANAGER\0" - - "_XSETROOT_ID\0" - - "_QT_SCROLL_DONE\0" - "_QT_INPUT_ENCODING\0" - - "_QT_CLOSE_CONNECTION\0" - - "_MOTIF_WM_HINTS\0" - - "DTWM_IS_RUNNING\0" - "ENLIGHTENMENT_DESKTOP\0" - "_DT_SAVE_MODE\0" - "_SGI_DESKS_MANAGER\0" - - // EWMH (aka NETWM) - "_NET_SUPPORTED\0" - "_NET_VIRTUAL_ROOTS\0" - "_NET_WORKAREA\0" - - "_NET_MOVERESIZE_WINDOW\0" - "_NET_WM_MOVERESIZE\0" - - "_NET_WM_NAME\0" - "_NET_WM_ICON_NAME\0" - "_NET_WM_ICON\0" - - "_NET_WM_PID\0" - - "_NET_WM_WINDOW_OPACITY\0" - - "_NET_WM_STATE\0" - "_NET_WM_STATE_ABOVE\0" - "_NET_WM_STATE_BELOW\0" - "_NET_WM_STATE_FULLSCREEN\0" - "_NET_WM_STATE_MAXIMIZED_HORZ\0" - "_NET_WM_STATE_MAXIMIZED_VERT\0" - "_NET_WM_STATE_MODAL\0" - "_NET_WM_STATE_STAYS_ON_TOP\0" - "_NET_WM_STATE_DEMANDS_ATTENTION\0" - - "_NET_WM_USER_TIME\0" - "_NET_WM_USER_TIME_WINDOW\0" - "_NET_WM_FULL_PLACEMENT\0" - - "_NET_WM_WINDOW_TYPE\0" - "_NET_WM_WINDOW_TYPE_DESKTOP\0" - "_NET_WM_WINDOW_TYPE_DOCK\0" - "_NET_WM_WINDOW_TYPE_TOOLBAR\0" - "_NET_WM_WINDOW_TYPE_MENU\0" - "_NET_WM_WINDOW_TYPE_UTILITY\0" - "_NET_WM_WINDOW_TYPE_SPLASH\0" - "_NET_WM_WINDOW_TYPE_DIALOG\0" - "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0" - "_NET_WM_WINDOW_TYPE_POPUP_MENU\0" - "_NET_WM_WINDOW_TYPE_TOOLTIP\0" - "_NET_WM_WINDOW_TYPE_NOTIFICATION\0" - "_NET_WM_WINDOW_TYPE_COMBO\0" - "_NET_WM_WINDOW_TYPE_DND\0" - "_NET_WM_WINDOW_TYPE_NORMAL\0" - "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0" - - "_KDE_NET_WM_FRAME_STRUT\0" - "_NET_FRAME_EXTENTS\0" - - "_NET_STARTUP_INFO\0" - "_NET_STARTUP_INFO_BEGIN\0" - - "_NET_SUPPORTING_WM_CHECK\0" - - "_NET_WM_CM_S0\0" - - "_NET_SYSTEM_TRAY_VISUAL\0" - - "_NET_ACTIVE_WINDOW\0" - - // Property formats - "TEXT\0" - "UTF8_STRING\0" - "CARDINAL\0" - - // xdnd - "XdndEnter\0" - "XdndPosition\0" - "XdndStatus\0" - "XdndLeave\0" - "XdndDrop\0" - "XdndFinished\0" - "XdndTypeList\0" - "XdndActionList\0" - - "XdndSelection\0" - - "XdndAware\0" - "XdndProxy\0" - - "XdndActionCopy\0" - "XdndActionLink\0" - "XdndActionMove\0" - "XdndActionPrivate\0" - - // Motif DND - "_MOTIF_DRAG_AND_DROP_MESSAGE\0" - "_MOTIF_DRAG_INITIATOR_INFO\0" - "_MOTIF_DRAG_RECEIVER_INFO\0" - "_MOTIF_DRAG_WINDOW\0" - "_MOTIF_DRAG_TARGETS\0" - - "XmTRANSFER_SUCCESS\0" - "XmTRANSFER_FAILURE\0" - - // Xkb - "_XKB_RULES_NAMES\0" - - // XEMBED - "_XEMBED\0" - "_XEMBED_INFO\0" - - // XInput2 - "Button Left\0" - "Button Middle\0" - "Button Right\0" - "Button Wheel Up\0" - "Button Wheel Down\0" - "Button Horiz Wheel Left\0" - "Button Horiz Wheel Right\0" - "Abs MT Position X\0" - "Abs MT Position Y\0" - "Abs MT Touch Major\0" - "Abs MT Touch Minor\0" - "Abs MT Orientation\0" - "Abs MT Pressure\0" - "Abs MT Tracking ID\0" - "Max Contacts\0" - "Rel X\0" - "Rel Y\0" - // XInput2 tablet - "Abs X\0" - "Abs Y\0" - "Abs Pressure\0" - "Abs Tilt X\0" - "Abs Tilt Y\0" - "Abs Wheel\0" - "Abs Distance\0" - "Wacom Serial IDs\0" - "INTEGER\0" - "Rel Horiz Wheel\0" - "Rel Vert Wheel\0" - "Rel Horiz Scroll\0" - "Rel Vert Scroll\0" - "_XSETTINGS_SETTINGS\0" - "_COMPIZ_DECOR_PENDING\0" - "_COMPIZ_DECOR_REQUEST\0" - "_COMPIZ_DECOR_DELETE_PIXMAP\0" - "_COMPIZ_TOOLKIT_ACTION\0" - "_GTK_LOAD_ICONTHEMES\0" - "AT_SPI_BUS\0" - "EDID\0" - "EDID_DATA\0" - "XFree86_DDC_EDID1_RAWDATA\0" - // \0\0 terminates loop. -}; - -QXcbAtom::Atom QXcbConnection::qatom(xcb_atom_t xatom) const -{ - return static_cast<QXcbAtom::Atom>(std::find(m_allAtoms, m_allAtoms + QXcbAtom::NAtoms, xatom) - m_allAtoms); -} - -void QXcbConnection::initializeAllAtoms() { - const char *names[QXcbAtom::NAtoms]; - const char *ptr = xcb_atomnames; - - int i = 0; - while (*ptr) { - names[i++] = ptr; - while (*ptr) - ++ptr; - ++ptr; - } - - Q_ASSERT(i == QXcbAtom::NAtoms); - - xcb_intern_atom_cookie_t cookies[QXcbAtom::NAtoms]; - - Q_ASSERT(i == QXcbAtom::NAtoms); - for (i = 0; i < QXcbAtom::NAtoms; ++i) - cookies[i] = xcb_intern_atom(xcb_connection(), false, strlen(names[i]), names[i]); - - for (i = 0; i < QXcbAtom::NAtoms; ++i) { - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_connection(), cookies[i], 0); - m_allAtoms[i] = reply->atom; - free(reply); - } -} - -xcb_atom_t QXcbConnection::internAtom(const char *name) -{ - if (!name || *name == 0) - return XCB_NONE; - - return Q_XCB_REPLY(xcb_intern_atom, xcb_connection(), false, strlen(name), name)->atom; -} - -QByteArray QXcbConnection::atomName(xcb_atom_t atom) -{ - if (!atom) - return QByteArray(); - - auto reply = Q_XCB_REPLY(xcb_get_atom_name, xcb_connection(), atom); - if (!reply) - qWarning() << "QXcbConnection::atomName: bad Atom" << atom; - else - return QByteArray(xcb_get_atom_name_name(reply.get()), xcb_get_atom_name_name_length(reply.get())); - - return QByteArray(); -} - const xcb_format_t *QXcbConnection::formatForDepth(uint8_t depth) const { xcb_format_iterator_t iterator = - xcb_setup_pixmap_formats_iterator(m_setup); + xcb_setup_pixmap_formats_iterator(setup()); while (iterator.rem) { xcb_format_t *format = iterator.data; @@ -2005,208 +1064,6 @@ void QXcbConnection::sync() free(xcb_get_input_focus_reply(xcb_connection(), cookie, 0)); } -void QXcbConnection::initializeShm() -{ - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_shm_id); - if (!reply || !reply->present) { - qCDebug(lcQpaXcb, "MIT-SHM extension is not present on the X server"); - return; - } - has_shm = true; - - auto shm_query = Q_XCB_REPLY(xcb_shm_query_version, m_connection); - if (shm_query) { - has_shm_fd = (shm_query->major_version == 1 && shm_query->minor_version >= 2) || - shm_query->major_version > 1; - } else { - qCWarning(lcQpaXcb, "QXcbConnection: Failed to request MIT-SHM version"); - } - - qCDebug(lcQpaXcb) << "Has MIT-SHM :" << has_shm; - qCDebug(lcQpaXcb) << "Has MIT-SHM FD :" << has_shm_fd; - - // Temporary disable warnings (unless running in debug mode). - auto logging = const_cast<QLoggingCategory*>(&lcQpaXcb()); - bool wasEnabled = logging->isEnabled(QtMsgType::QtWarningMsg); - if (!logging->isEnabled(QtMsgType::QtDebugMsg)) - logging->setEnabled(QtMsgType::QtWarningMsg, false); - if (!QXcbBackingStore::createSystemVShmSegment(this)) { - qCDebug(lcQpaXcb, "failed to create System V shared memory segment (remote " - "X11 connection?), disabling SHM"); - has_shm = has_shm_fd = false; - } - if (wasEnabled) - logging->setEnabled(QtMsgType::QtWarningMsg, true); -} - -void QXcbConnection::initializeXFixes() -{ - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_xfixes_id); - if (!reply || !reply->present) - return; - - auto xfixes_query = Q_XCB_REPLY(xcb_xfixes_query_version, m_connection, - XCB_XFIXES_MAJOR_VERSION, - XCB_XFIXES_MINOR_VERSION); - if (!xfixes_query || xfixes_query->major_version < 2) { - qWarning("QXcbConnection: Failed to initialize XFixes"); - return; - } - xfixes_first_event = reply->first_event; - has_xfixes = true; -} - -void QXcbConnection::initializeXRender() -{ -#if QT_CONFIG(xcb_render) - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_render_id); - if (!reply || !reply->present) { - qCDebug(lcQpaXcb, "XRender extension not present on the X server"); - return; - } - - auto xrender_query = Q_XCB_REPLY(xcb_render_query_version, m_connection, - XCB_RENDER_MAJOR_VERSION, - XCB_RENDER_MINOR_VERSION); - if (!xrender_query) { - qCWarning(lcQpaXcb, "xcb_render_query_version failed"); - return; - } - - has_render_extension = true; - m_xrenderVersion.first = xrender_query->major_version; - m_xrenderVersion.second = xrender_query->minor_version; -#endif -} - -void QXcbConnection::initializeXRandr() -{ - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_randr_id); - if (!reply || !reply->present) - return; - - xrandr_first_event = reply->first_event; - - auto xrandr_query = Q_XCB_REPLY(xcb_randr_query_version, m_connection, - XCB_RANDR_MAJOR_VERSION, - XCB_RANDR_MINOR_VERSION); - - has_randr_extension = true; - - if (!xrandr_query || (xrandr_query->major_version < 1 || (xrandr_query->major_version == 1 && xrandr_query->minor_version < 2))) { - qWarning("QXcbConnection: Failed to initialize XRandr"); - has_randr_extension = false; - } - - xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(m_setup); - for (; rootIter.rem; xcb_screen_next(&rootIter)) { - xcb_randr_select_input(xcb_connection(), - rootIter.data->root, - XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | - XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | - XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | - XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY - ); - } -} - -void QXcbConnection::initializeXinerama() -{ - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_xinerama_id); - if (!reply || !reply->present) - return; - - auto xinerama_is_active = Q_XCB_REPLY(xcb_xinerama_is_active, m_connection); - has_xinerama_extension = xinerama_is_active && xinerama_is_active->state; -} - -void QXcbConnection::initializeXShape() -{ - const xcb_query_extension_reply_t *xshape_reply = xcb_get_extension_data(m_connection, &xcb_shape_id); - if (!xshape_reply || !xshape_reply->present) - return; - - has_shape_extension = true; - auto shape_query = Q_XCB_REPLY(xcb_shape_query_version, m_connection); - if (!shape_query) { - qWarning("QXcbConnection: Failed to initialize SHAPE extension"); - } else if (shape_query->major_version > 1 || (shape_query->major_version == 1 && shape_query->minor_version >= 1)) { - // The input shape is the only thing added in SHAPE 1.1 - has_input_shape = true; - } -} - -void QXcbConnection::initializeXKB() -{ -#if QT_CONFIG(xkb) - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_xkb_id); - if (!reply || !reply->present) { - qWarning("Qt: XKEYBOARD extension not present on the X server."); - xkb_first_event = 0; - return; - } - xkb_first_event = reply->first_event; - - xcb_connection_t *c = connection()->xcb_connection(); - - auto xkb_query = Q_XCB_REPLY(xcb_xkb_use_extension, c, - XKB_X11_MIN_MAJOR_XKB_VERSION, - XKB_X11_MIN_MINOR_XKB_VERSION); - - if (!xkb_query) { - qWarning("Qt: Failed to initialize XKB extension"); - return; - } else if (!xkb_query->supported) { - qWarning("Qt: Unsupported XKB version (We want %d %d, but X server has %d %d)", - XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION, - xkb_query->serverMajor, xkb_query->serverMinor); - return; - } - - has_xkb = true; - - const uint16_t required_map_parts = (XCB_XKB_MAP_PART_KEY_TYPES | - XCB_XKB_MAP_PART_KEY_SYMS | - XCB_XKB_MAP_PART_MODIFIER_MAP | - XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | - XCB_XKB_MAP_PART_KEY_ACTIONS | - XCB_XKB_MAP_PART_KEY_BEHAVIORS | - XCB_XKB_MAP_PART_VIRTUAL_MODS | - XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP); - - const uint16_t required_events = (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | - XCB_XKB_EVENT_TYPE_MAP_NOTIFY | - XCB_XKB_EVENT_TYPE_STATE_NOTIFY); - - // XKB events are reported to all interested clients without regard - // to the current keyboard input focus or grab state - xcb_void_cookie_t select = xcb_xkb_select_events_checked(c, - XCB_XKB_ID_USE_CORE_KBD, - required_events, - 0, - required_events, - required_map_parts, - required_map_parts, - 0); - - xcb_generic_error_t *error = xcb_request_check(c, select); - if (error) { - free(error); - qWarning("Qt: failed to select notify events from xcb-xkb"); - return; - } -#endif -} - -void QXcbConnection::initializeXSync() -{ - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(xcb_connection(), &xcb_sync_id); - if (!reply || !reply->present) - return; - - has_sync_extension = true; -} - QXcbSystemTrayTracker *QXcbConnection::systemTrayTracker() const { if (!m_systemTrayTracker) { diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index db45031cf4..49e79ec3fd 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -47,32 +47,18 @@ #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 <cstdlib> -#include <memory> - -// This is needed to make Qt compile together with XKB. xkb.h is using a variable -// which is called 'explicit', this is a reserved keyword in c++ -#if QT_CONFIG(xkb) -#define explicit dont_use_cxx_explicit -#include <xcb/xkb.h> -#undef explicit -#endif +#include "qxcbeventqueue.h" +#include "qxcbconnection_basic.h" #if QT_CONFIG(tabletevent) #include <QTabletEvent> #endif -struct xcb_randr_get_output_info_reply_t; - QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQpaXInput) @@ -80,10 +66,10 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaXInputDevices) Q_DECLARE_LOGGING_CATEGORY(lcQpaXInputEvents) Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) Q_DECLARE_LOGGING_CATEGORY(lcQpaEvents) -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; @@ -96,244 +82,6 @@ class QXcbNativeInterface; class QXcbSystemTrayTracker; class QXcbGlIntegration; -namespace QXcbAtom { - enum Atom { - // window-manager <-> client protocols - WM_PROTOCOLS, - WM_DELETE_WINDOW, - WM_TAKE_FOCUS, - _NET_WM_PING, - _NET_WM_CONTEXT_HELP, - _NET_WM_SYNC_REQUEST, - _NET_WM_SYNC_REQUEST_COUNTER, - MANAGER, // System tray notification - _NET_SYSTEM_TRAY_OPCODE, // System tray operation - - // ICCCM window state - WM_STATE, - WM_CHANGE_STATE, - WM_CLASS, - WM_NAME, - - // Session management - WM_CLIENT_LEADER, - WM_WINDOW_ROLE, - SM_CLIENT_ID, - WM_CLIENT_MACHINE, - - // Clipboard - CLIPBOARD, - INCR, - TARGETS, - MULTIPLE, - TIMESTAMP, - SAVE_TARGETS, - CLIP_TEMPORARY, - _QT_SELECTION, - _QT_CLIPBOARD_SENTINEL, - _QT_SELECTION_SENTINEL, - CLIPBOARD_MANAGER, - - RESOURCE_MANAGER, - - _XSETROOT_ID, - - _QT_SCROLL_DONE, - _QT_INPUT_ENCODING, - - // Qt/XCB specific - _QT_CLOSE_CONNECTION, - - _MOTIF_WM_HINTS, - - DTWM_IS_RUNNING, - ENLIGHTENMENT_DESKTOP, - _DT_SAVE_MODE, - _SGI_DESKS_MANAGER, - - // EWMH (aka NETWM) - _NET_SUPPORTED, - _NET_VIRTUAL_ROOTS, - _NET_WORKAREA, - - _NET_MOVERESIZE_WINDOW, - _NET_WM_MOVERESIZE, - - _NET_WM_NAME, - _NET_WM_ICON_NAME, - _NET_WM_ICON, - - _NET_WM_PID, - - _NET_WM_WINDOW_OPACITY, - - _NET_WM_STATE, - _NET_WM_STATE_ABOVE, - _NET_WM_STATE_BELOW, - _NET_WM_STATE_FULLSCREEN, - _NET_WM_STATE_MAXIMIZED_HORZ, - _NET_WM_STATE_MAXIMIZED_VERT, - _NET_WM_STATE_MODAL, - _NET_WM_STATE_STAYS_ON_TOP, - _NET_WM_STATE_DEMANDS_ATTENTION, - - _NET_WM_USER_TIME, - _NET_WM_USER_TIME_WINDOW, - _NET_WM_FULL_PLACEMENT, - - _NET_WM_WINDOW_TYPE, - _NET_WM_WINDOW_TYPE_DESKTOP, - _NET_WM_WINDOW_TYPE_DOCK, - _NET_WM_WINDOW_TYPE_TOOLBAR, - _NET_WM_WINDOW_TYPE_MENU, - _NET_WM_WINDOW_TYPE_UTILITY, - _NET_WM_WINDOW_TYPE_SPLASH, - _NET_WM_WINDOW_TYPE_DIALOG, - _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, - _NET_WM_WINDOW_TYPE_POPUP_MENU, - _NET_WM_WINDOW_TYPE_TOOLTIP, - _NET_WM_WINDOW_TYPE_NOTIFICATION, - _NET_WM_WINDOW_TYPE_COMBO, - _NET_WM_WINDOW_TYPE_DND, - _NET_WM_WINDOW_TYPE_NORMAL, - _KDE_NET_WM_WINDOW_TYPE_OVERRIDE, - - _KDE_NET_WM_FRAME_STRUT, - _NET_FRAME_EXTENTS, - - _NET_STARTUP_INFO, - _NET_STARTUP_INFO_BEGIN, - - _NET_SUPPORTING_WM_CHECK, - - _NET_WM_CM_S0, - - _NET_SYSTEM_TRAY_VISUAL, - - _NET_ACTIVE_WINDOW, - - // Property formats - TEXT, - UTF8_STRING, - CARDINAL, - - // Xdnd - XdndEnter, - XdndPosition, - XdndStatus, - XdndLeave, - XdndDrop, - XdndFinished, - XdndTypelist, - XdndActionList, - - XdndSelection, - - XdndAware, - XdndProxy, - - XdndActionCopy, - XdndActionLink, - XdndActionMove, - XdndActionPrivate, - - // Motif DND - _MOTIF_DRAG_AND_DROP_MESSAGE, - _MOTIF_DRAG_INITIATOR_INFO, - _MOTIF_DRAG_RECEIVER_INFO, - _MOTIF_DRAG_WINDOW, - _MOTIF_DRAG_TARGETS, - - XmTRANSFER_SUCCESS, - XmTRANSFER_FAILURE, - - // Xkb - _XKB_RULES_NAMES, - - // XEMBED - _XEMBED, - _XEMBED_INFO, - - // XInput2 - ButtonLeft, - ButtonMiddle, - ButtonRight, - ButtonWheelUp, - ButtonWheelDown, - ButtonHorizWheelLeft, - ButtonHorizWheelRight, - AbsMTPositionX, - AbsMTPositionY, - AbsMTTouchMajor, - AbsMTTouchMinor, - AbsMTOrientation, - AbsMTPressure, - AbsMTTrackingID, - MaxContacts, - RelX, - RelY, - // XInput2 tablet - AbsX, - AbsY, - AbsPressure, - AbsTiltX, - AbsTiltY, - AbsWheel, - AbsDistance, - WacomSerialIDs, - INTEGER, - RelHorizWheel, - RelVertWheel, - RelHorizScroll, - RelVertScroll, - - _XSETTINGS_SETTINGS, - - _COMPIZ_DECOR_PENDING, - _COMPIZ_DECOR_REQUEST, - _COMPIZ_DECOR_DELETE_PIXMAP, - _COMPIZ_TOOLKIT_ACTION, - _GTK_LOAD_ICONTHEMES, - - AT_SPI_BUS, - - EDID, - EDID_DATA, - XFree86_DDC_EDID1_RAWDATA, - - NAtoms - }; -} - -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,38 +123,23 @@ private: QXcbWindow *m_window; }; -class QAbstractEventDispatcher; -class Q_XCB_EXPORT QXcbConnection : public QObject +class Q_XCB_EXPORT QXcbConnection : public QXcbBasicConnection { Q_OBJECT public: - explicit QXcbConnection(xcb_connection_t *c, int primaryScreenNumber, - QXcbNativeInterface *nativeInterface, bool canGrabServer, - xcb_visualid_t defaultVisualId, const QByteArray &displayName, - void *xlibDisplay = nullptr); - + QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, xcb_visualid_t defaultVisualId, const char *displayName = 0); ~QXcbConnection(); - static QXcbConnection *create(QXcbNativeInterface *nativeInterface, bool canGrabServer, - xcb_visualid_t defaultVisualId, const char *displayName = nullptr); - 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; } - int primaryScreenNumber() const { return m_primaryScreenNumber; } - QXcbVirtualDesktop *primaryVirtualDesktop() const { return m_virtualDesktops.value(m_primaryScreenNumber); } + QXcbVirtualDesktop *primaryVirtualDesktop() const { + return m_virtualDesktops.value(primaryScreenNumber()); + } QXcbScreen *primaryScreen() const; - inline xcb_atom_t atom(QXcbAtom::Atom atom) const { return m_allAtoms[atom]; } - QXcbAtom::Atom qatom(xcb_atom_t atom) const; - xcb_atom_t internAtom(const char *name); - QByteArray atomName(xcb_atom_t atom); - - const char *displayName() const { return m_displayName.constData(); } - xcb_connection_t *xcb_connection() const { return m_connection; } - const xcb_setup_t *setup() const { return m_setup; } const xcb_format_t *formatForDepth(uint8_t depth) const; bool imageNeedsEndianSwap() const @@ -414,9 +147,9 @@ public: if (!hasShm()) return false; // The non-Shm path does its own swapping #if Q_BYTE_ORDER == Q_BIG_ENDIAN - return m_setup->image_byte_order != XCB_IMAGE_ORDER_MSB_FIRST; + return setup()->image_byte_order != XCB_IMAGE_ORDER_MSB_FIRST; #else - return m_setup->image_byte_order != XCB_IMAGE_ORDER_LSB_FIRST; + return setup()->image_byte_order != XCB_IMAGE_ORDER_LSB_FIRST; #endif } @@ -436,9 +169,6 @@ public: bool hasDefaultVisualId() const { return m_defaultVisualId != UINT_MAX; } xcb_visualid_t defaultVisualId() const { return m_defaultVisualId; } -#if QT_CONFIG(xcb_xlib) - void *xlib_display() const { return m_xlib_display; } -#endif void sync(); void handleXcbError(xcb_generic_error_t *error); @@ -452,44 +182,15 @@ 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; } inline xcb_timestamp_t netWmUserTime() const { return m_netWmUserTime; } inline void setNetWmUserTime(xcb_timestamp_t t) { if (t > m_netWmUserTime) m_netWmUserTime = t; } - bool hasXFixes() const { return has_xfixes; } - bool hasXShape() const { return has_shape_extension; } - bool hasXRandr() const { return has_randr_extension; } - bool hasInputShape() const { return has_input_shape; } - bool hasXKB() const { return has_xkb; } - bool hasXRender(int major = -1, int minor = -1) const - { - if (has_render_extension && major != -1 && minor != -1) - return m_xrenderVersion >= qMakePair(major, minor); - - return has_render_extension; - } - bool hasXInput2() const { return m_xi2Enabled; } - bool hasShm() const { return has_shm; } - bool hasShmFd() const { return has_shm_fd; } - bool hasXSync() const { return has_sync_extension; } - xcb_timestamp_t getTimestamp(); xcb_window_t getSelectionOwner(xcb_atom_t atom) const; xcb_window_t getQtSelectionOwner(); @@ -523,46 +224,33 @@ public: Qt::MouseButtons queryMouseButtons() const; Qt::KeyboardModifiers queryKeyboardModifiers() const; + bool isUserInputEvent(xcb_generic_event_t *event) const; + #if QT_CONFIG(xcb_xinput) void xi2SelectStateEvents(); void xi2SelectDeviceEvents(xcb_window_t window); void xi2SelectDeviceEventsCompatibility(xcb_window_t window); bool xi2SetMouseGrabEnabled(xcb_window_t w, bool grab); bool xi2MouseEventsDisabled() const; - bool isAtLeastXI21() const { return m_xi2Enabled && m_xi2Minor >= 1; } - bool isAtLeastXI22() const { return m_xi2Enabled && m_xi2Minor >= 2; } Qt::MouseButton xiToQtMouseButton(uint32_t b); void xi2UpdateScrollingDevices(); bool startSystemMoveResizeForTouchBegin(xcb_window_t window, const QPoint &point, int corner); 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(xcb_connection()); } + void processXcbEvents(QEventLoop::ProcessEventsFlags flags); + 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(); - void initializeXRandr(); - void initializeXinerama(); - void initializeXShape(); - void initializeXKB(); - void initializeXSync(); + void selectXRandrEvents(); QXcbScreen* findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc) const; QXcbScreen* findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output) const; QXcbVirtualDesktop* virtualDesktopForRootWindow(xcb_window_t rootWindow) const; @@ -574,12 +262,9 @@ 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) - int m_xi2Minor = -1; - void initializeXInput2(); void xi2SetupDevice(void *info, bool removeExisting = true); void xi2SetupDevices(); struct TouchDeviceData { @@ -605,7 +290,6 @@ private: void xi2HandleEvent(xcb_ge_event_t *event); void xi2HandleHierarchyEvent(void *event); void xi2HandleDeviceChangedEvent(void *event); - int m_xiOpCode; void xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow); #if QT_CONFIG(tabletevent) struct TabletData { @@ -646,24 +330,25 @@ private: ScrollingDevice *scrollingDeviceForId(int id); static bool xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value); -#endif - xcb_connection_t *const m_connection; - const xcb_setup_t *m_setup = nullptr; + QHash<int, TouchDeviceData> m_touchDevices; + struct StartSystemMoveResizeInfo { + xcb_window_t window = XCB_NONE; + uint16_t deviceid; + uint32_t pointid; + int corner; + } m_startSystemMoveResizeInfo; +#endif // QT_CONFIG(xcb_xinput) + const bool m_canGrabServer; const xcb_visualid_t m_defaultVisualId; QList<QXcbVirtualDesktop *> m_virtualDesktops; QList<QXcbScreen *> m_screens; - const int m_primaryScreenNumber; - - xcb_atom_t m_allAtoms[QXcbAtom::NAtoms]; xcb_timestamp_t m_time = XCB_CURRENT_TIME; xcb_timestamp_t m_netWmUserTime = XCB_CURRENT_TIME; - const QByteArray m_displayName; - QXcbKeyboard *m_keyboard = nullptr; #ifndef QT_NO_CLIPBOARD QXcbClipboard *m_clipboard = nullptr; @@ -674,44 +359,12 @@ private: QScopedPointer<QXcbWMSupport> m_wmSupport; QXcbNativeInterface *m_nativeInterface = nullptr; -#if QT_CONFIG(xcb_xlib) - void *const m_xlib_display; -#endif - QXcbEventReader *m_reader = nullptr; + QXcbEventQueue *m_eventQueue = nullptr; -#if QT_CONFIG(xcb_xinput) - QHash<int, TouchDeviceData> m_touchDevices; - struct StartSystemMoveResizeInfo { - xcb_window_t window = XCB_NONE; - uint16_t deviceid; - uint32_t pointid; - int corner; - } m_startSystemMoveResizeInfo; -#endif WindowMapper m_mapper; QVector<PeekFunc> m_peekFuncs; - uint32_t xfixes_first_event = 0; - uint32_t xrandr_first_event = 0; - uint32_t xkb_first_event = 0; -#if QT_CONFIG(xcb_xinput) - uint32_t xinput_first_event = 0; -#endif - - bool has_xfixes = false; - bool has_xinerama_extension = false; - bool has_shape_extension = false; - bool has_randr_extension = false; - bool has_input_shape; - bool has_xkb = false; - bool has_render_extension = false; - bool has_shm = false; - bool has_shm_fd = false; - bool has_sync_extension = false; - - QPair<int, int> m_xrenderVersion; - Qt::MouseButtons m_buttonState = 0; Qt::MouseButton m_button = Qt::NoButton; @@ -729,11 +382,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; }; @@ -744,24 +393,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: @@ -772,22 +403,6 @@ private: QXcbConnection *m_connection; }; -#define Q_XCB_REPLY_CONNECTION_ARG(connection, ...) connection - -struct QStdFreeDeleter { - void operator()(void *p) const Q_DECL_NOTHROW { return std::free(p); } -}; - -#define Q_XCB_REPLY(call, ...) \ - std::unique_ptr<call##_reply_t, QStdFreeDeleter>( \ - call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call(__VA_ARGS__), nullptr) \ - ) - -#define Q_XCB_REPLY_UNCHECKED(call, ...) \ - std::unique_ptr<call##_reply_t, QStdFreeDeleter>( \ - call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call##_unchecked(__VA_ARGS__), nullptr) \ - ) - // The xcb_send_event() requires all events to have 32 bytes. It calls memcpy() on the // passed in event. If the passed in event is less than 32 bytes, memcpy() reaches into // unrelated memory. diff --git a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp new file mode 100644 index 0000000000..7bed3c8937 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** 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 "qxcbconnection_basic.h" +#include "qxcbbackingstore.h" // for createSystemVShmSegment() + +#include <xcb/randr.h> +#include <xcb/shm.h> +#include <xcb/sync.h> +#include <xcb/xfixes.h> +#include <xcb/xinerama.h> +#if QT_CONFIG(xcb_xinput) +#include <xcb/xinput.h> +#endif +#if QT_CONFIG(xcb_render) +#include <xcb/render.h> +#endif +#if QT_CONFIG(xkb) +#define explicit dont_use_cxx_explicit +#include <xcb/xkb.h> +#undef explicit +#endif + +#if QT_CONFIG(xcb_xlib) +#define register /* C++17 deprecated register */ +#include <X11/Xlib.h> +#include <X11/Xlib-xcb.h> +#include <X11/Xlibint.h> +#include <X11/Xutil.h> +#undef register +#endif + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb") + +#if QT_CONFIG(xcb_xlib) +static const char * const xcbConnectionErrors[] = { + "No error", /* Error 0 */ + "I/O error", /* XCB_CONN_ERROR */ + "Unsupported extension used", /* XCB_CONN_CLOSED_EXT_NOTSUPPORTED */ + "Out of memory", /* XCB_CONN_CLOSED_MEM_INSUFFICIENT */ + "Maximum allowed requested length exceeded", /* XCB_CONN_CLOSED_REQ_LEN_EXCEED */ + "Failed to parse display string", /* XCB_CONN_CLOSED_PARSE_ERR */ + "No such screen on display", /* XCB_CONN_CLOSED_INVALID_SCREEN */ + "Error during FD passing" /* XCB_CONN_CLOSED_FDPASSING_FAILED */ +}; + +static int nullErrorHandler(Display *dpy, XErrorEvent *err) +{ +#ifndef Q_XCB_DEBUG + Q_UNUSED(dpy); + Q_UNUSED(err); +#else + const int buflen = 1024; + char buf[buflen]; + + XGetErrorText(dpy, err->error_code, buf, buflen); + fprintf(stderr, "X Error: serial %lu error %d %s\n", err->serial, (int) err->error_code, buf); +#endif + return 0; +} + +static int ioErrorHandler(Display *dpy) +{ + xcb_connection_t *conn = XGetXCBConnection(dpy); + if (conn != NULL) { + /* Print a message with a textual description of the error */ + int code = xcb_connection_has_error(conn); + const char *str = "Unknown error"; + int arrayLength = sizeof(xcbConnectionErrors) / sizeof(xcbConnectionErrors[0]); + if (code >= 0 && code < arrayLength) + str = xcbConnectionErrors[code]; + + qWarning("The X11 connection broke: %s (code %d)", str, code); + } + return _XDefaultIOError(dpy); +} +#endif + +QXcbBasicConnection::QXcbBasicConnection(const char *displayName) + : m_displayName(displayName ? QByteArray(displayName) : qgetenv("DISPLAY")) +{ +#if QT_CONFIG(xcb_xlib) + Display *dpy = XOpenDisplay(m_displayName.constData()); + if (dpy) { + m_primaryScreenNumber = DefaultScreen(dpy); + m_xcbConnection = XGetXCBConnection(dpy); + XSetEventQueueOwner(dpy, XCBOwnsEventQueue); + XSetErrorHandler(nullErrorHandler); + XSetIOErrorHandler(ioErrorHandler); + m_xlibDisplay = dpy; + } +#else + m_xcbConnection = xcb_connect(m_displayName.constData(), &m_primaryScreenNumber); +#endif + if (Q_UNLIKELY(!isConnected())) { + qCWarning(lcQpaXcb, "could not connect to display %s", m_displayName.constData()); + return; + } + + m_setup = xcb_get_setup(m_xcbConnection); + m_xcbAtom.initialize(m_xcbConnection); + + xcb_extension_t *extensions[] = { + &xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id, +#if QT_CONFIG(xkb) + &xcb_xkb_id, +#endif +#if QT_CONFIG(xcb_render) + &xcb_render_id, +#endif +#if QT_CONFIG(xcb_xinput) + &xcb_input_id, +#endif + 0 + }; + + for (xcb_extension_t **ext_it = extensions; *ext_it; ++ext_it) + xcb_prefetch_extension_data (m_xcbConnection, *ext_it); + + initializeXSync(); + if (!qEnvironmentVariableIsSet("QT_XCB_NO_MITSHM")) + initializeShm(); + if (!qEnvironmentVariableIsSet("QT_XCB_NO_XRANDR")) + initializeXRandr(); + if (!m_hasXRandr) + initializeXinerama(); + initializeXFixes(); + initializeXRender(); +#if QT_CONFIG(xcb_xinput) + if (!qEnvironmentVariableIsSet("QT_XCB_NO_XI2")) + initializeXInput2(); +#endif + initializeXShape(); + initializeXKB(); +} + +QXcbBasicConnection::~QXcbBasicConnection() +{ + if (isConnected()) { +#if QT_CONFIG(xcb_xlib) + XCloseDisplay(static_cast<Display *>(m_xlibDisplay)); +#else + xcb_disconnect(m_xcbConnection); +#endif + } +} + +xcb_atom_t QXcbBasicConnection::internAtom(const char *name) +{ + if (!name || *name == 0) + return XCB_NONE; + + return Q_XCB_REPLY(xcb_intern_atom, m_xcbConnection, false, strlen(name), name)->atom; +} + +QByteArray QXcbBasicConnection::atomName(xcb_atom_t atom) +{ + if (!atom) + return QByteArray(); + + auto reply = Q_XCB_REPLY(xcb_get_atom_name, m_xcbConnection, atom); + if (reply) + return QByteArray(xcb_get_atom_name_name(reply.get()), xcb_get_atom_name_name_length(reply.get())); + + qCWarning(lcQpaXcb) << "atomName: bad atom" << atom; + return QByteArray(); +} + +#if QT_CONFIG(xcb_xinput) +// Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed: +// - "pad0" became "extension" +// - "pad1" and "pad" became "pad0" +// New and old version of this struct share the following fields: +typedef struct qt_xcb_ge_event_t { + uint8_t response_type; + uint8_t extension; + uint16_t sequence; + uint32_t length; + uint16_t event_type; +} qt_xcb_ge_event_t; + +bool QXcbBasicConnection::isXIEvent(xcb_generic_event_t *event) const +{ + qt_xcb_ge_event_t *e = reinterpret_cast<qt_xcb_ge_event_t *>(event); + return e->extension == m_xiOpCode; +} + +bool QXcbBasicConnection::isXIType(xcb_generic_event_t *event, uint16_t type) const +{ + if (!isXIEvent(event)) + return false; + + auto *e = reinterpret_cast<qt_xcb_ge_event_t *>(event); + return e->event_type == type; +} +#endif // QT_CONFIG(xcb_xinput) + +bool QXcbBasicConnection::isXFixesType(uint responseType, int eventType) const +{ + return m_hasXFixes && responseType == m_xfixesFirstEvent + eventType; +} + +bool QXcbBasicConnection::isXRandrType(uint responseType, int eventType) const +{ + return m_hasXRandr && responseType == m_xrandrFirstEvent + eventType; +} + +bool QXcbBasicConnection::isXkbType(uint responseType) const +{ + return m_hasXkb && responseType == m_xkbFirstEvent; +} + +void QXcbBasicConnection::initializeXSync() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_sync_id); + if (!reply || !reply->present) + return; + + m_hasXSync = true; +} + +void QXcbBasicConnection::initializeShm() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_shm_id); + if (!reply || !reply->present) { + qCDebug(lcQpaXcb, "MIT-SHM extension is not present on the X server"); + return; + } + + auto shmQuery = Q_XCB_REPLY(xcb_shm_query_version, m_xcbConnection); + if (!shmQuery) { + qCWarning(lcQpaXcb, "failed to request MIT-SHM version"); + return; + } + + m_hasShm = true; + m_hasShmFd = (shmQuery->major_version == 1 && shmQuery->minor_version >= 2) || + shmQuery->major_version > 1; + + qCDebug(lcQpaXcb) << "Has MIT-SHM :" << m_hasShm; + qCDebug(lcQpaXcb) << "Has MIT-SHM FD :" << m_hasShmFd; + + // Temporary disable warnings (unless running in debug mode). + auto logging = const_cast<QLoggingCategory*>(&lcQpaXcb()); + bool wasEnabled = logging->isEnabled(QtMsgType::QtWarningMsg); + if (!logging->isEnabled(QtMsgType::QtDebugMsg)) + logging->setEnabled(QtMsgType::QtWarningMsg, false); + if (!QXcbBackingStore::createSystemVShmSegment(m_xcbConnection)) { + qCDebug(lcQpaXcb, "failed to create System V shared memory segment (remote " + "X11 connection?), disabling SHM"); + m_hasShm = m_hasShmFd = false; + } + if (wasEnabled) + logging->setEnabled(QtMsgType::QtWarningMsg, true); +} + +void QXcbBasicConnection::initializeXRandr() +{ +#if QT_CONFIG(xcb_render) + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_render_id); + if (!reply || !reply->present) { + qCDebug(lcQpaXcb, "XRender extension not present on the X server"); + return; + } + + auto xrenderQuery = Q_XCB_REPLY(xcb_render_query_version, m_xcbConnection, + XCB_RENDER_MAJOR_VERSION, + XCB_RENDER_MINOR_VERSION); + if (!xrenderQuery) { + qCWarning(lcQpaXcb, "xcb_render_query_version failed"); + return; + } + + m_hasXRender = true; + m_xrenderVersion.first = xrenderQuery->major_version; + m_xrenderVersion.second = xrenderQuery->minor_version; +#endif +} + +void QXcbBasicConnection::initializeXinerama() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_xinerama_id); + if (!reply || !reply->present) + return; + + auto xineramaActive = Q_XCB_REPLY(xcb_xinerama_is_active, m_xcbConnection); + if (xineramaActive && xineramaActive->state) + m_hasXinerama = true; +} + +void QXcbBasicConnection::initializeXFixes() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_xfixes_id); + if (!reply || !reply->present) + return; + + auto xfixesQuery = Q_XCB_REPLY(xcb_xfixes_query_version, m_xcbConnection, + XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION); + if (!xfixesQuery || xfixesQuery->major_version < 2) { + qCWarning(lcQpaXcb, "failed to initialize XFixes"); + return; + } + + m_hasXFixes = true; + m_xfixesFirstEvent = reply->first_event; +} + +void QXcbBasicConnection::initializeXRender() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_randr_id); + if (!reply || !reply->present) + return; + + auto xrandrQuery = Q_XCB_REPLY(xcb_randr_query_version, m_xcbConnection, + XCB_RANDR_MAJOR_VERSION, + XCB_RANDR_MINOR_VERSION); + if (!xrandrQuery || (xrandrQuery->major_version < 1 || + (xrandrQuery->major_version == 1 && xrandrQuery->minor_version < 2))) { + qCWarning(lcQpaXcb, "failed to initialize XRandr"); + return; + } + + m_hasXRandr = true; + m_xrandrFirstEvent = reply->first_event; +} + +#if QT_CONFIG(xcb_xinput) +void QXcbBasicConnection::initializeXInput2() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_input_id); + if (!reply || !reply->present) { + qCDebug(lcQpaXcb, "XInput extension is not present on the X server"); + return; + } + + auto xinputQuery = Q_XCB_REPLY(xcb_input_xi_query_version, m_xcbConnection, 2, 2); + if (!xinputQuery || xinputQuery->major_version != 2) { + qCWarning(lcQpaXcb, "X server does not support XInput 2"); + return; + } + + qCDebug(lcQpaXcb, "Using XInput version %d.%d", + xinputQuery->major_version, xinputQuery->minor_version); + + m_xi2Enabled = true; + m_xiOpCode = reply->major_opcode; + m_xinputFirstEvent = reply->first_event; + m_xi2Minor = xinputQuery->minor_version; +} +#endif + +void QXcbBasicConnection::initializeXShape() +{ + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_shape_id); + if (!reply || !reply->present) + return; + + m_hasXhape = true; + + auto shapeQuery = Q_XCB_REPLY(xcb_shape_query_version, m_xcbConnection); + if (!shapeQuery) { + qCWarning(lcQpaXcb, "failed to initialize XShape extension"); + return; + } + + if (shapeQuery->major_version > 1 || (shapeQuery->major_version == 1 && shapeQuery->minor_version >= 1)) { + // The input shape is the only thing added in SHAPE 1.1 + m_hasInputShape = true; + } +} + +void QXcbBasicConnection::initializeXKB() +{ +#if QT_CONFIG(xkb) + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_xcbConnection, &xcb_xkb_id); + if (!reply || !reply->present) { + qCWarning(lcQpaXcb, "XKeyboard extension not present on the X server"); + return; + } + + int wantMajor = 1; + int wantMinor = 0; + auto xkbQuery = Q_XCB_REPLY(xcb_xkb_use_extension, m_xcbConnection, wantMajor, wantMinor); + if (!xkbQuery) { + qCWarning(lcQpaXcb, "failed to initialize XKeyboard extension"); + return; + } + if (!xkbQuery->supported) { + qCWarning(lcQpaXcb, "unsupported XKB version (we want %d.%d, but X server has %d.%d)", + wantMajor, wantMinor, xkbQuery->serverMajor, xkbQuery->serverMinor); + return; + } + + m_hasXkb = true; + m_xkbFirstEvent = reply->first_event; +#endif +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbconnection_basic.h b/src/plugins/platforms/xcb/qxcbconnection_basic.h new file mode 100644 index 0000000000..ca91f7fa45 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbconnection_basic.h @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** 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 QXCBBASICCONNECTION_H +#define QXCBBASICCONNECTION_H + +#include "qxcbatom.h" + +#include <QtCore/QPair> +#include <QtCore/QObject> +#include <QtCore/QByteArray> +#include <QtCore/QLoggingCategory> +#include <QtGui/private/qtguiglobal_p.h> + +#include <xcb/xcb.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcQpaXcb) + +class QXcbBasicConnection : public QObject +{ + Q_OBJECT +public: + QXcbBasicConnection(const char *displayName); + ~QXcbBasicConnection(); + +#if QT_CONFIG(xcb_xlib) + void *xlib_display() const { return m_xlibDisplay; } +#endif + const char *displayName() const { return m_displayName.constData(); } + int primaryScreenNumber() const { return m_primaryScreenNumber; } + xcb_connection_t *xcb_connection() const { return m_xcbConnection; } + bool isConnected() const { + return m_xcbConnection && !xcb_connection_has_error(m_xcbConnection); + } + const xcb_setup_t *setup() const { return m_setup; } + + inline xcb_atom_t atom(QXcbAtom::Atom qatom) const { return m_xcbAtom.atom(qatom); } + QXcbAtom::Atom qatom(xcb_atom_t atom) const { return m_xcbAtom.qatom(atom); } + xcb_atom_t internAtom(const char *name); + QByteArray atomName(xcb_atom_t atom); + + bool hasXFixes() const { return m_hasXFixes; } + bool hasXShape() const { return m_hasXhape; } + bool hasXRandr() const { return m_hasXRandr; } + bool hasInputShape() const { return m_hasInputShape; } + bool hasXKB() const { return m_hasXkb; } + bool hasXRender(int major = -1, int minor = -1) const { + if (m_hasXRender && major != -1 && minor != -1) + return m_xrenderVersion >= qMakePair(major, minor); + + return m_hasXRender; + } + bool hasXInput2() const { return m_xi2Enabled; } + bool hasShm() const { return m_hasShm; } + bool hasShmFd() const { return m_hasShmFd; } + bool hasXSync() const { return m_hasXSync; } + bool hasXinerama() const { return m_hasXinerama; } + +#if QT_CONFIG(xcb_xinput) + bool isAtLeastXI21() const { return m_xi2Enabled && m_xi2Minor >= 1; } + bool isAtLeastXI22() const { return m_xi2Enabled && m_xi2Minor >= 2; } + bool isXIEvent(xcb_generic_event_t *event) const; + bool isXIType(xcb_generic_event_t *event, uint16_t type) const; +#endif + + bool isXFixesType(uint responseType, int eventType) const; + bool isXRandrType(uint responseType, int eventType) const; + bool isXkbType(uint responseType) const; // https://bugs.freedesktop.org/show_bug.cgi?id=51295 + +protected: + void initializeShm(); + void initializeXFixes(); + void initializeXRender(); + void initializeXRandr(); + void initializeXinerama(); + void initializeXShape(); + void initializeXKB(); + void initializeXSync(); +#if QT_CONFIG(xcb_xinput) + void initializeXInput2(); +#endif + +private: +#if QT_CONFIG(xcb_xlib) + void *m_xlibDisplay = nullptr; +#endif + QByteArray m_displayName; + xcb_connection_t *m_xcbConnection = nullptr; + int m_primaryScreenNumber = 0; + const xcb_setup_t *m_setup = nullptr; + QXcbAtom m_xcbAtom; + + bool m_hasXFixes = false; + bool m_hasXinerama = false; + bool m_hasXhape = false; + bool m_hasInputShape; + bool m_hasXRandr = false; + bool m_hasXkb = false; + bool m_hasXRender = false; + bool m_hasShm = false; + bool m_hasShmFd = false; + bool m_hasXSync = false; + + QPair<int, int> m_xrenderVersion; + + bool m_xi2Enabled = false; +#if QT_CONFIG(xcb_xinput) + int m_xi2Minor = -1; + int m_xiOpCode = -1; + uint32_t m_xinputFirstEvent = 0; +#endif + + uint32_t m_xfixesFirstEvent = 0; + uint32_t m_xrandrFirstEvent = 0; + uint32_t m_xkbFirstEvent = 0; +}; + +#define Q_XCB_REPLY_CONNECTION_ARG(connection, ...) connection + +struct QStdFreeDeleter { + void operator()(void *p) const Q_DECL_NOTHROW { return std::free(p); } +}; + +#define Q_XCB_REPLY(call, ...) \ + std::unique_ptr<call##_reply_t, QStdFreeDeleter>( \ + call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call(__VA_ARGS__), nullptr) \ + ) + +#define Q_XCB_REPLY_UNCHECKED(call, ...) \ + std::unique_ptr<call##_reply_t, QStdFreeDeleter>( \ + call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call##_unchecked(__VA_ARGS__), nullptr) \ + ) + +QT_END_NAMESPACE + +#endif // QXCBBASICCONNECTION_H diff --git a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp new file mode 100644 index 0000000000..4c380bf39f --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp @@ -0,0 +1,416 @@ +/**************************************************************************** +** +** 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 "qxcbconnection.h" +#include "qxcbscreen.h" +#include "qxcbintegration.h" + +#include <QtGui/private/qhighdpiscaling_p.h> +#include <QtCore/QString> +#include <QtCore/QList> + +#include <xcb/xinerama.h> + +void QXcbConnection::selectXRandrEvents() +{ + xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(setup()); + for (; rootIter.rem; xcb_screen_next(&rootIter)) { + xcb_randr_select_input(xcb_connection(), + rootIter.data->root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | + XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY + ); + } +} + +QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc) const +{ + for (QXcbScreen *screen : m_screens) { + if (screen->root() == rootWindow && screen->crtc() == crtc) + return screen; + } + + return nullptr; +} + +QXcbScreen* QXcbConnection::findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output) const +{ + for (QXcbScreen *screen : m_screens) { + if (screen->root() == rootWindow && screen->output() == output) + return screen; + } + + return nullptr; +} + +QXcbVirtualDesktop* QXcbConnection::virtualDesktopForRootWindow(xcb_window_t rootWindow) const +{ + for (QXcbVirtualDesktop *virtualDesktop : m_virtualDesktops) { + if (virtualDesktop->screen()->root == rootWindow) + return virtualDesktop; + } + + return nullptr; +} + +/*! + \brief Synchronizes the screen list, adds new screens, removes deleted ones +*/ +void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event) +{ + if (event->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) { + xcb_randr_crtc_change_t crtc = event->u.cc; + QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(crtc.window); + if (!virtualDesktop) + // Not for us + return; + + QXcbScreen *screen = findScreenForCrtc(crtc.window, crtc.crtc); + qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_CRTC_CHANGE:" << crtc.crtc + << "mode" << crtc.mode << "relevant screen" << screen; + // Only update geometry when there's a valid mode on the CRTC + // CRTC with node mode could mean that output has been disabled, and we'll + // get RRNotifyOutputChange notification for that. + if (screen && crtc.mode) { + if (crtc.rotation == XCB_RANDR_ROTATION_ROTATE_90 || + crtc.rotation == XCB_RANDR_ROTATION_ROTATE_270) + std::swap(crtc.width, crtc.height); + screen->updateGeometry(QRect(crtc.x, crtc.y, crtc.width, crtc.height), crtc.rotation); + if (screen->mode() != crtc.mode) + screen->updateRefreshRate(crtc.mode); + } + + } else if (event->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) { + xcb_randr_output_change_t output = event->u.oc; + QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(output.window); + if (!virtualDesktop) + // Not for us + return; + + QXcbScreen *screen = findScreenForOutput(output.window, output.output); + qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_OUTPUT_CHANGE:" << output.output; + + if (screen && output.connection == XCB_RANDR_CONNECTION_DISCONNECTED) { + qCDebug(lcQpaScreen) << "screen" << screen->name() << "has been disconnected"; + destroyScreen(screen); + } else if (!screen && output.connection == XCB_RANDR_CONNECTION_CONNECTED) { + // New XRandR output is available and it's enabled + if (output.crtc != XCB_NONE && output.mode != XCB_NONE) { + auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(), + output.output, output.config_timestamp); + // Find a fake screen + const auto scrs = virtualDesktop->screens(); + for (QPlatformScreen *scr : scrs) { + QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(scr); + if (xcbScreen->output() == XCB_NONE) { + screen = xcbScreen; + break; + } + } + + if (screen) { + QString nameWas = screen->name(); + // Transform the fake screen into a physical screen + screen->setOutput(output.output, outputInfo.get()); + updateScreen(screen, output); + qCDebug(lcQpaScreen) << "output" << screen->name() + << "is connected and enabled; was fake:" << nameWas; + } else { + screen = createScreen(virtualDesktop, output, outputInfo.get()); + qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled"; + } + QHighDpiScaling::updateHighDpiScaling(); + } + } else if (screen) { + if (output.crtc == XCB_NONE && output.mode == XCB_NONE) { + // Screen has been disabled + auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(), + output.output, output.config_timestamp); + if (outputInfo->crtc == XCB_NONE) { + qCDebug(lcQpaScreen) << "output" << screen->name() << "has been disabled"; + destroyScreen(screen); + } else { + qCDebug(lcQpaScreen) << "output" << screen->name() << "has been temporarily disabled for the mode switch"; + // Reset crtc to skip RRCrtcChangeNotify events, + // because they may be invalid in the middle of the mode switch + screen->setCrtc(XCB_NONE); + } + } else { + updateScreen(screen, output); + qCDebug(lcQpaScreen) << "output has changed" << screen; + } + } + + qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name(); + } +} + +bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output) +{ + auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), rootWindow); + if (!primary) + qWarning("failed to get the primary output of the screen"); + + const bool isPrimary = primary ? (primary->output == output) : false; + + return isPrimary; +} + +void QXcbConnection::updateScreen(QXcbScreen *screen, const xcb_randr_output_change_t &outputChange) +{ + screen->setCrtc(outputChange.crtc); // Set the new crtc, because it can be invalid + screen->updateGeometry(outputChange.config_timestamp); + if (screen->mode() != outputChange.mode) + screen->updateRefreshRate(outputChange.mode); + // Only screen which belongs to the primary virtual desktop can be a primary screen + if (screen->screenNumber() == primaryScreenNumber()) { + if (!screen->isPrimary() && checkOutputIsPrimary(outputChange.window, outputChange.output)) { + screen->setPrimary(true); + + // If the screen became primary, reshuffle the order in QGuiApplicationPrivate + const int idx = m_screens.indexOf(screen); + if (idx > 0) { + qAsConst(m_screens).first()->setPrimary(false); + m_screens.swap(0, idx); + } + screen->virtualDesktop()->setPrimaryScreen(screen); + QXcbIntegration::instance()->setPrimaryScreen(screen); + } + } +} + +QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop, + const xcb_randr_output_change_t &outputChange, + xcb_randr_get_output_info_reply_t *outputInfo) +{ + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputChange.output, outputInfo); + // Only screen which belongs to the primary virtual desktop can be a primary screen + if (screen->screenNumber() == primaryScreenNumber()) + screen->setPrimary(checkOutputIsPrimary(outputChange.window, outputChange.output)); + + if (screen->isPrimary()) { + if (!m_screens.isEmpty()) + qAsConst(m_screens).first()->setPrimary(false); + + m_screens.prepend(screen); + } else { + m_screens.append(screen); + } + virtualDesktop->addScreen(screen); + QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary()); + + return screen; +} + +void QXcbConnection::destroyScreen(QXcbScreen *screen) +{ + QXcbVirtualDesktop *virtualDesktop = screen->virtualDesktop(); + if (virtualDesktop->screens().count() == 1) { + // If there are no other screens on the same virtual desktop, + // then transform the physical screen into a fake screen. + const QString nameWas = screen->name(); + screen->setOutput(XCB_NONE, nullptr); + qCDebug(lcQpaScreen) << "transformed" << nameWas << "to fake" << screen; + } else { + // There is more than one screen on the same virtual desktop, remove the screen + m_screens.removeOne(screen); + virtualDesktop->removeScreen(screen); + + // When primary screen is removed, set the new primary screen + // which belongs to the primary virtual desktop. + if (screen->isPrimary()) { + QXcbScreen *newPrimary = static_cast<QXcbScreen *>(virtualDesktop->screens().at(0)); + newPrimary->setPrimary(true); + const int idx = m_screens.indexOf(newPrimary); + if (idx > 0) + m_screens.swap(0, idx); + QXcbIntegration::instance()->setPrimaryScreen(newPrimary); + } + + QXcbIntegration::instance()->destroyScreen(screen); + } +} + +void QXcbConnection::initializeScreens() +{ + selectXRandrEvents(); + + xcb_screen_iterator_t it = xcb_setup_roots_iterator(setup()); + int xcbScreenNumber = 0; // screen number in the xcb sense + QXcbScreen *primaryScreen = nullptr; + while (it.rem) { + // Each "screen" in xcb terminology is a virtual desktop, + // potentially a collection of separate juxtaposed monitors. + // But we want a separate QScreen for each output (e.g. DVI-I-1, VGA-1, etc.) + // which will become virtual siblings. + xcb_screen_t *xcbScreen = it.data; + QXcbVirtualDesktop *virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber); + m_virtualDesktops.append(virtualDesktop); + QList<QPlatformScreen *> siblings; + if (hasXRender()) { + // RRGetScreenResourcesCurrent is fast but it may return nothing if the + // configuration is not initialized wrt to the hardware. We should call + // RRGetScreenResources in this case. + auto resources_current = Q_XCB_REPLY(xcb_randr_get_screen_resources_current, + xcb_connection(), xcbScreen->root); + if (!resources_current) { + qWarning("failed to get the current screen resources"); + } else { + xcb_timestamp_t timestamp = 0; + xcb_randr_output_t *outputs = nullptr; + int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources_current.get()); + if (outputCount) { + timestamp = resources_current->config_timestamp; + outputs = xcb_randr_get_screen_resources_current_outputs(resources_current.get()); + } else { + auto resources = Q_XCB_REPLY(xcb_randr_get_screen_resources, + xcb_connection(), xcbScreen->root); + if (!resources) { + qWarning("failed to get the screen resources"); + } else { + timestamp = resources->config_timestamp; + outputCount = xcb_randr_get_screen_resources_outputs_length(resources.get()); + outputs = xcb_randr_get_screen_resources_outputs(resources.get()); + } + } + + if (outputCount) { + auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), xcbScreen->root); + if (!primary) { + qWarning("failed to get the primary output of the screen"); + } else { + for (int i = 0; i < outputCount; i++) { + auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info, + xcb_connection(), outputs[i], timestamp); + // Invalid, disconnected or disabled output + if (!output) + continue; + + if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) { + qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable( + QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()), + xcb_randr_get_output_info_name_length(output.get())))); + continue; + } + + if (output->crtc == XCB_NONE) { + qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable( + QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()), + xcb_randr_get_output_info_name_length(output.get())))); + continue; + } + + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputs[i], output.get()); + siblings << screen; + m_screens << screen; + + // There can be multiple outputs per screen, use either + // the first or an exact match. An exact match isn't + // always available if primary->output is XCB_NONE + // or currently disconnected output. + if (primaryScreenNumber() == xcbScreenNumber) { + if (!primaryScreen || (primary && outputs[i] == primary->output)) { + if (primaryScreen) + primaryScreen->setPrimary(false); + primaryScreen = screen; + primaryScreen->setPrimary(true); + siblings.prepend(siblings.takeLast()); + } + } + } + } + } + } + } else if (hasXinerama()) { + // Xinerama is available + auto screens = Q_XCB_REPLY(xcb_xinerama_query_screens, xcb_connection()); + if (screens) { + xcb_xinerama_screen_info_iterator_t it = xcb_xinerama_query_screens_screen_info_iterator(screens.get()); + while (it.rem) { + xcb_xinerama_screen_info_t *screen_info = it.data; + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, + XCB_NONE, nullptr, + screen_info, it.index); + siblings << screen; + m_screens << screen; + xcb_xinerama_screen_info_next(&it); + } + } + } + if (siblings.isEmpty()) { + // If there are no XRandR outputs or XRandR extension is missing, + // then create a fake/legacy screen. + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, nullptr); + qCDebug(lcQpaScreen) << "created fake screen" << screen; + m_screens << screen; + if (primaryScreenNumber() == xcbScreenNumber) { + primaryScreen = screen; + primaryScreen->setPrimary(true); + } + siblings << screen; + } + virtualDesktop->setScreens(siblings); + xcb_screen_next(&it); + ++xcbScreenNumber; + } // for each xcb screen + + for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops)) + virtualDesktop->subscribeToXFixesSelectionNotify(); + + if (m_virtualDesktops.isEmpty()) { + qFatal("QXcbConnection: no screens available"); + } else { + // Ensure the primary screen is first on the list + if (primaryScreen) { + if (qAsConst(m_screens).first() != primaryScreen) { + m_screens.removeOne(primaryScreen); + m_screens.prepend(primaryScreen); + } + } + + // Push the screens to QGuiApplication + for (QXcbScreen *screen : qAsConst(m_screens)) { + qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")"; + QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary()); + } + + qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name(); + } +} diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index a3befc7384..04ddd3c98c 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -51,31 +51,6 @@ using qt_xcb_input_device_event_t = xcb_input_button_press_event_t; -void QXcbConnection::initializeXInput2() -{ - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_input_id); - if (!reply || !reply->present) { - qCDebug(lcQpaXInput, "XInput extension is not present on the X server"); - return; - } - - m_xiOpCode = reply->major_opcode; - xinput_first_event = reply->first_event; - - auto xinput_query = Q_XCB_REPLY(xcb_input_xi_query_version, m_connection, 2, 2); - - if (!xinput_query || xinput_query->major_version != 2) { - qCWarning(lcQpaXInput, "X server does not support XInput 2"); - } else { - qCDebug(lcQpaXInput, "Using XInput version %d.%d", - xinput_query->major_version, xinput_query->minor_version); - m_xi2Minor = xinput_query->minor_version; - m_xi2Enabled = true; - xi2SetupDevices(); - xi2SelectStateEvents(); - } -} - struct qt_xcb_input_event_mask_t { xcb_input_event_mask_t header; uint32_t mask; @@ -91,7 +66,7 @@ void QXcbConnection::xi2SelectStateEvents() xiEventMask.mask = XCB_INPUT_XI_EVENT_MASK_HIERARCHY; xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_DEVICE_CHANGED; xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_PROPERTY; - xcb_input_xi_select_events(m_connection, rootWindow(), 1, &xiEventMask.header); + xcb_input_xi_select_events(xcb_connection(), rootWindow(), 1, &xiEventMask.header); } void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window) @@ -117,8 +92,8 @@ void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window) mask.header.mask_len = 1; mask.mask = bitMask; xcb_void_cookie_t cookie = - xcb_input_xi_select_events_checked(m_connection, window, 1, &mask.header); - xcb_generic_error_t *error = xcb_request_check(m_connection, cookie); + xcb_input_xi_select_events_checked(xcb_connection(), window, 1, &mask.header); + xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie); if (error) { qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code); free(error); @@ -310,7 +285,7 @@ void QXcbConnection::xi2SetupDevices() m_touchDevices.clear(); m_xiMasterPointerIds.clear(); - auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, m_connection, XCB_INPUT_DEVICE_ALL); + auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL); if (!reply) { qCDebug(lcQpaXInputDevices) << "failed to query devices"; return; @@ -387,8 +362,8 @@ void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window) xiMask.mask = mask; xcb_void_cookie_t cookie = - xcb_input_xi_select_events_checked(m_connection, window, 1, &xiMask.header); - xcb_generic_error_t *error = xcb_request_check(m_connection, cookie); + xcb_input_xi_select_events_checked(xcb_connection(), window, 1, &xiMask.header); + xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie); if (error) { qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code); free(error); @@ -413,7 +388,7 @@ void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window) xiEventMask[i].header.mask_len = 1; xiEventMask[i].mask = mask; } - xcb_input_xi_select_events(m_connection, window, nrTablets, &(xiEventMask.data()->header)); + xcb_input_xi_select_events(xcb_connection(), window, nrTablets, &(xiEventMask.data()->header)); } #endif @@ -430,7 +405,7 @@ void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window) xiEventMask[i].mask = mask; i++; } - xcb_input_xi_select_events(m_connection, window, i, &(xiEventMask.data()->header)); + xcb_input_xi_select_events(xcb_connection(), window, i, &(xiEventMask.data()->header)); } } @@ -633,7 +608,7 @@ bool QXcbConnection::xi2MouseEventsDisabled() const static bool xi2MouseDisabled = qEnvironmentVariableIsSet("QT_XCB_NO_XI2_MOUSE"); // FIXME: Don't use XInput2 mouse events when Xinerama extension // is enabled, because it causes problems with multi-monitor setup. - return xi2MouseDisabled || has_xinerama_extension; + return xi2MouseDisabled || hasXinerama(); } bool QXcbConnection::isTouchScreen(int id) @@ -745,7 +720,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo // Touches must be accepted when we are grabbing touch events. Otherwise the entire sequence // will get replayed when the grab ends. if (m_xiGrab) { - xcb_input_xi_allow_events(m_connection, XCB_CURRENT_TIME, xiDeviceEvent->deviceid, + xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid, XCB_INPUT_EVENT_MODE_ACCEPT_TOUCH, xiDeviceEvent->detail, xiDeviceEvent->event); } @@ -771,7 +746,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo xiDeviceEvent->detail == m_startSystemMoveResizeInfo.pointid) { QXcbWindow *window = platformWindowFromId(m_startSystemMoveResizeInfo.window); if (window) { - xcb_input_xi_allow_events(m_connection, XCB_CURRENT_TIME, xiDeviceEvent->deviceid, + xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid, XCB_INPUT_EVENT_MODE_REJECT_TOUCH, xiDeviceEvent->detail, xiDeviceEvent->event); window->doStartSystemMoveResize(QPoint(x, y), m_startSystemMoveResizeInfo.corner); @@ -850,10 +825,10 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) for (int id : m_xiMasterPointerIds) { xcb_generic_error_t *error = nullptr; - auto cookie = xcb_input_xi_grab_device(m_connection, w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id, + auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id, XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC, false, 1, &mask); - auto *reply = xcb_input_xi_grab_device_reply(m_connection, cookie, &error); + auto *reply = xcb_input_xi_grab_device_reply(xcb_connection(), cookie, &error); if (error) { qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x" "(error code %d)", id, w, error->error_code); @@ -867,8 +842,8 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) } } else { // ungrab for (int id : m_xiMasterPointerIds) { - auto cookie = xcb_input_xi_ungrab_device_checked(m_connection, XCB_CURRENT_TIME, id); - xcb_generic_error_t *error = xcb_request_check(m_connection, cookie); + auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id); + xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie); if (error) { qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code); free(error); @@ -911,7 +886,7 @@ void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event); switch (xiEvent->reason) { case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: { - auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, m_connection, xiEvent->sourceid); + auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid); if (!reply || reply->num_infos <= 0) return; auto it = xcb_input_xi_query_device_infos_iterator(reply.get()); @@ -931,7 +906,7 @@ void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) void QXcbConnection::xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice) { - auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, m_connection, scrollingDevice.deviceId); + auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), scrollingDevice.deviceId); if (!reply || reply->num_infos <= 0) { qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId); return; @@ -1184,7 +1159,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD _WACSER_COUNT }; - auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, m_connection, tabletData->deviceId, 0, + auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0, ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100); if (reply) { if (reply->type == atom(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) { diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index 2b8e507f30..aa329d8cb7 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -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; diff --git a/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp b/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp new file mode 100644 index 0000000000..3cb2a5b5ef --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** 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 "qxcbeventdispatcher.h" +#include "qxcbconnection.h" + +#include <QtCore/QCoreApplication> + +#include <qpa/qwindowsysteminterface.h> + +QT_BEGIN_NAMESPACE + +QXcbUnixEventDispatcher::QXcbUnixEventDispatcher(QXcbConnection *connection, QObject *parent) + : QEventDispatcherUNIX(parent) + , m_connection(connection) +{ +} + +QXcbUnixEventDispatcher::~QXcbUnixEventDispatcher() +{ +} + +bool QXcbUnixEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + const bool didSendEvents = QEventDispatcherUNIX::processEvents(flags); + m_connection->processXcbEvents(flags); + // The following line should not be necessary after QTBUG-70095 + return QWindowSystemInterface::sendWindowSystemEvents(flags) || didSendEvents; +} + +bool QXcbUnixEventDispatcher::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); + return qGlobalPostedEventsCount() || QWindowSystemInterface::windowSystemEventsQueued(); +} + +void QXcbUnixEventDispatcher::flush() +{ + if (qApp) + qApp->sendPostedEvents(); +} + +#if QT_CONFIG(glib) +struct XcbEventSource +{ + GSource source; + QXcbGlibEventDispatcher *dispatcher; + QXcbGlibEventDispatcherPrivate *dispatcher_p; + QXcbConnection *connection = nullptr; +}; + +static gboolean xcbSourcePrepare(GSource *source, gint *timeout) +{ + Q_UNUSED(timeout) + auto xcbEventSource = reinterpret_cast<XcbEventSource *>(source); + return xcbEventSource->dispatcher_p->wakeUpCalled; +} + +static gboolean xcbSourceCheck(GSource *source) +{ + return xcbSourcePrepare(source, nullptr); +} + +static gboolean xcbSourceDispatch(GSource *source, GSourceFunc, gpointer) +{ + auto xcbEventSource = reinterpret_cast<XcbEventSource *>(source); + QEventLoop::ProcessEventsFlags flags = xcbEventSource->dispatcher->flags(); + xcbEventSource->connection->processXcbEvents(flags); + // The following line should not be necessary after QTBUG-70095 + QWindowSystemInterface::sendWindowSystemEvents(flags); + return true; +} + +QXcbGlibEventDispatcher::QXcbGlibEventDispatcher(QXcbConnection *connection, QObject *parent) + : QEventDispatcherGlib(*new QXcbGlibEventDispatcherPrivate(), parent) +{ + Q_D(QXcbGlibEventDispatcher); + + m_xcbEventSourceFuncs.prepare = xcbSourcePrepare; + m_xcbEventSourceFuncs.check = xcbSourceCheck; + m_xcbEventSourceFuncs.dispatch = xcbSourceDispatch; + m_xcbEventSourceFuncs.finalize = nullptr; + + m_xcbEventSource = reinterpret_cast<XcbEventSource *>( + g_source_new(&m_xcbEventSourceFuncs, sizeof(XcbEventSource))); + + m_xcbEventSource->dispatcher = this; + m_xcbEventSource->dispatcher_p = d_func(); + m_xcbEventSource->connection = connection; + + g_source_set_can_recurse(&m_xcbEventSource->source, true); + g_source_attach(&m_xcbEventSource->source, d->mainContext); +} + +QXcbGlibEventDispatcherPrivate::QXcbGlibEventDispatcherPrivate() +{ +} + +QXcbGlibEventDispatcher::~QXcbGlibEventDispatcher() +{ + g_source_destroy(&m_xcbEventSource->source); + g_source_unref(&m_xcbEventSource->source); +} + +bool QXcbGlibEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + m_flags = flags; + return QEventDispatcherGlib::processEvents(m_flags); +} + +#endif // QT_CONFIG(glib) + +QAbstractEventDispatcher *QXcbEventDispatcher::createEventDispatcher(QXcbConnection *connection) +{ +#if QT_CONFIG(glib) + if (qEnvironmentVariableIsEmpty("QT_NO_GLIB") && QEventDispatcherGlib::versionSupported()) { + qCDebug(lcQpaXcb, "using glib dispatcher"); + return new QXcbGlibEventDispatcher(connection); + } else +#endif + { + qCDebug(lcQpaXcb, "using unix dispatcher"); + return new QXcbUnixEventDispatcher(connection); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbeventdispatcher.h b/src/plugins/platforms/xcb/qxcbeventdispatcher.h new file mode 100644 index 0000000000..6aadd63a70 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbeventdispatcher.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** 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 QXCBEVENTDISPATCHER_H +#define QXCBEVENTDISPATCHER_H + +#include <QtCore/QObject> +#include <QtCore/QEventLoop> + +#include <QtCore/private/qeventdispatcher_unix_p.h> +#if QT_CONFIG(glib) +#include <QtCore/private/qeventdispatcher_glib_p.h> +#include <glib.h> +#endif + +QT_BEGIN_NAMESPACE + +class QXcbConnection; + +class QXcbUnixEventDispatcher : public QEventDispatcherUNIX +{ + Q_OBJECT +public: + explicit QXcbUnixEventDispatcher(QXcbConnection *connection, QObject *parent = nullptr); + ~QXcbUnixEventDispatcher(); + bool processEvents(QEventLoop::ProcessEventsFlags flags) override; + + // Maybe some user code depends on deprecated QUnixEventDispatcherQPA:: + // hasPendingEvents() / flush() implementation, so keep it around until + // Qt 6. These methods are deprecated in QAbstractEventDispatcher. + bool hasPendingEvents() override; // ### Qt 6 remove + void flush() override; // ### Qt 6 remove + +private: + QXcbConnection *m_connection; +}; + +#if QT_CONFIG(glib) + +struct XcbEventSource; +class QXcbGlibEventDispatcherPrivate; + +class QXcbGlibEventDispatcher : public QEventDispatcherGlib +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QXcbGlibEventDispatcher) + +public: + explicit QXcbGlibEventDispatcher(QXcbConnection *connection, QObject *parent = nullptr); + ~QXcbGlibEventDispatcher(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags) override; + QEventLoop::ProcessEventsFlags flags() const { return m_flags; } + +private: + XcbEventSource *m_xcbEventSource; + GSourceFuncs m_xcbEventSourceFuncs; + QEventLoop::ProcessEventsFlags m_flags; +}; + +class QXcbGlibEventDispatcherPrivate : public QEventDispatcherGlibPrivate +{ + Q_DECLARE_PUBLIC(QXcbGlibEventDispatcher) + +public: + QXcbGlibEventDispatcherPrivate(); +}; + +#endif // QT_CONFIG(glib) + +class QXcbEventDispatcher +{ +public: + static QAbstractEventDispatcher *createEventDispatcher(QXcbConnection *connection); + +private: + QXcbConnection *m_connection; +}; + +QT_END_NAMESPACE + +#endif // QXCBEVENTDISPATCHER_H diff --git a/src/plugins/platforms/xcb/qxcbeventqueue.cpp b/src/plugins/platforms/xcb/qxcbeventqueue.cpp new file mode 100644 index 0000000000..862f68764b --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbeventqueue.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** 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/QCoreApplication> +#include <QtCore/QAbstractEventDispatcher> +#include <QtCore/QMutex> +#include <QtCore/QDebug> + +QT_BEGIN_NAMESPACE + +static QBasicMutex qAppExiting; +static bool dispatcherOwnerDestructing = false; + +/*! + \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) +{ + // When running test cases in auto tests, static variables are preserved + // between test function runs, even if Q*Application object is destroyed. + // Reset to default value to account for this. + dispatcherOwnerDestructing = false; + qAddPostRoutine([]() { + QMutexLocker locker(&qAppExiting); + dispatcherOwnerDestructing = true; + }); + + // 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; +} + +xcb_generic_event_t *QXcbEventQueue::takeFirst(QEventLoop::ProcessEventsFlags flags) +{ + // This is the level at which we were moving excluded user input events into + // separate queue in Qt 4 (see qeventdispatcher_x11.cpp). In this case + // QXcbEventQueue represents Xlib's internal event queue. In Qt 4, Xlib's + // event queue peeking APIs would not see these events anymore, the same way + // our peeking functions do not consider m_inputEvents. This design is + // intentional to keep the same behavior. We could do filtering directly on + // QXcbEventQueue, without the m_inputEvents, but it is not clear if it is + // needed by anyone who peeks at the native event queue. + + bool excludeUserInputEvents = flags.testFlag(QEventLoop::ExcludeUserInputEvents); + if (excludeUserInputEvents) { + xcb_generic_event_t *event = nullptr; + while ((event = takeFirst())) { + if (m_connection->isUserInputEvent(event)) { + m_inputEvents << event; + continue; + } + break; + } + return event; + } + + if (!m_inputEvents.isEmpty()) + return m_inputEvents.takeFirst(); + return takeFirst(); +} + +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); + wakeUpDispatcher(); + } + + if (!m_closeConnectionDetected) { + // Connection was terminated not by us. Wake up dispatcher, which will + // call processXcbEvents(), where we handle the connection errors via + // xcb_connection_has_error(). + wakeUpDispatcher(); + } +} + +void QXcbEventQueue::wakeUpDispatcher() +{ + QMutexLocker locker(&qAppExiting); + if (!dispatcherOwnerDestructing) { + // This thread can run before a dispatcher has been created, + // so check if it is ready. + if (QCoreApplication::eventDispatcher()) + QCoreApplication::eventDispatcher()->wakeUp(); + } +} + +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->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 diff --git a/src/plugins/platforms/xcb/qxcbeventqueue.h b/src/plugins/platforms/xcb/qxcbeventqueue.h new file mode 100644 index 0000000000..235f2824be --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbeventqueue.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** 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 <QtCore/QEventLoop> +#include <QtCore/QVector> + +#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; + + bool isEmpty() const { return m_head == m_flushedTail && !m_head->event; } + xcb_generic_event_t *takeFirst(QEventLoop::ProcessEventsFlags flags); + xcb_generic_event_t *takeFirst(); + void flushBufferedEvents(); + void wakeUpDispatcher(); + + // ### 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); + +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; + + QVector<xcb_generic_event_t *> m_inputEvents; + + // 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 diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index db8dc09025..ed9e87a036 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -46,6 +46,8 @@ #include "qxcbbackingstore.h" #include "qxcbnativeinterface.h" #include "qxcbclipboard.h" +#include "qxcbeventqueue.h" +#include "qxcbeventdispatcher.h" #if QT_CONFIG(draganddrop) #include "qxcbdrag.h" #endif @@ -57,7 +59,6 @@ #include <xcb/xcb.h> -#include <QtEventDispatcherSupport/private/qgenericunixeventdispatcher_p.h> #include <QtFontDatabaseSupport/private/qgenericunixfontdatabase_p.h> #include <QtServiceSupport/private/qgenericunixservices_p.h> @@ -136,6 +137,8 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char m_instance = this; qApp->setAttribute(Qt::AA_CompressHighFrequencyEvents, true); + QWindowSystemInterface::setPlatformFiltersEvents(true); + qRegisterMetaType<QXcbWindow*>(); #if QT_CONFIG(xcb_xlib) XInitThreads(); @@ -193,14 +196,22 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char const int numParameters = parameters.size(); m_connections.reserve(1 + numParameters / 2); - if (QXcbConnection *defaultConnection = QXcbConnection::create(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, displayName)) { - m_connections.append(defaultConnection); - for (int i = 0; i < numParameters - 1; i += 2) { - qCDebug(lcQpaScreen) << "connecting to additional display: " << parameters.at(i) << parameters.at(i+1); - QString display = parameters.at(i) + QLatin1Char(':') + parameters.at(i+1); - if (QXcbConnection *connection = QXcbConnection::create(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, display.toLatin1().constData())) - m_connections.append(connection); - } + auto conn = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, displayName); + if (!conn->isConnected()) { + delete conn; + return; + } + m_connections << conn; + + // ### Qt 6 (QTBUG-52408) remove this multi-connection code path + for (int i = 0; i < numParameters - 1; i += 2) { + qCDebug(lcQpaXcb) << "connecting to additional display: " << parameters.at(i) << parameters.at(i+1); + QString display = parameters.at(i) + QLatin1Char(':') + parameters.at(i+1); + conn = new QXcbConnection(m_nativeInterface.data(), m_canGrab, m_defaultVisualId, display.toLatin1().constData()); + if (conn->isConnected()) + m_connections << conn; + else + delete conn; } m_fontDatabase.reset(new QGenericUnixFontDatabase()); @@ -332,10 +343,7 @@ bool QXcbIntegration::hasCapability(QPlatformIntegration::Capability cap) const QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const { - QAbstractEventDispatcher *dispatcher = createUnixEventDispatcher(); - for (int i = 0; i < m_connections.size(); i++) - m_connections[i]->eventReader()->registerEventDispatcher(dispatcher); - return dispatcher; + return QXcbEventDispatcher::createEventDispatcher(defaultConnection()); } void QXcbIntegration::initialize() diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h index a2de22d53d..f13e232291 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.h +++ b/src/plugins/platforms/xcb/qxcbintegration.h @@ -53,7 +53,6 @@ QT_BEGIN_NAMESPACE class QXcbConnection; class QAbstractEventDispatcher; class QXcbNativeInterface; -class QXcbScreen; class Q_XCB_EXPORT QXcbIntegration : public QPlatformIntegration { diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp index 20c169fc53..c5dc7b21ad 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp +++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp @@ -1190,9 +1190,10 @@ QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection) #if QT_CONFIG(xkb) core_device_id = 0; if (connection->hasXKB()) { + selectEvents(); core_device_id = xkb_x11_get_core_keyboard_device_id(xcb_connection()); if (core_device_id == -1) { - qWarning("Qt: couldn't get core keyboard device info"); + qCWarning(lcQpaXcb, "failed to get core keyboard device info"); return; } } else { @@ -1210,6 +1211,42 @@ QXcbKeyboard::~QXcbKeyboard() xcb_key_symbols_free(m_key_symbols); } +void QXcbKeyboard::selectEvents() +{ +#if QT_CONFIG(xkb) + const uint16_t required_map_parts = (XCB_XKB_MAP_PART_KEY_TYPES | + XCB_XKB_MAP_PART_KEY_SYMS | + XCB_XKB_MAP_PART_MODIFIER_MAP | + XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | + XCB_XKB_MAP_PART_KEY_ACTIONS | + XCB_XKB_MAP_PART_KEY_BEHAVIORS | + XCB_XKB_MAP_PART_VIRTUAL_MODS | + XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP); + + const uint16_t required_events = (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | + XCB_XKB_EVENT_TYPE_MAP_NOTIFY | + XCB_XKB_EVENT_TYPE_STATE_NOTIFY); + + // XKB events are reported to all interested clients without regard + // to the current keyboard input focus or grab state + xcb_void_cookie_t select = xcb_xkb_select_events_checked( + xcb_connection(), + XCB_XKB_ID_USE_CORE_KBD, + required_events, + 0, + required_events, + required_map_parts, + required_map_parts, + 0); + + xcb_generic_error_t *error = xcb_request_check(xcb_connection(), select); + if (error) { + free(error); + qCWarning(lcQpaXcb, "failed to select notify events from XKB"); + } +#endif +} + void QXcbKeyboard::updateVModMapping() { #if QT_CONFIG(xkb) @@ -1541,7 +1578,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 +1587,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, m_autoRepeatCode = code; } return true; - }, false /* removeFromQueue */); + }); } bool filtered = false; diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.h b/src/plugins/platforms/xcb/qxcbkeyboard.h index 95915fb2e6..f8490592b7 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.h +++ b/src/plugins/platforms/xcb/qxcbkeyboard.h @@ -43,6 +43,11 @@ #include "qxcbobject.h" #include <xcb/xcb_keysyms.h> +#if QT_CONFIG(xkb) +#define explicit dont_use_cxx_explicit +#include <xcb/xkb.h> +#undef explicit +#endif #include <xkbcommon/xkbcommon.h> #if QT_CONFIG(xkb) @@ -62,6 +67,8 @@ public: ~QXcbKeyboard(); + void selectEvents(); + void handleKeyPressEvent(const xcb_key_press_event_t *event); void handleKeyReleaseEvent(const xcb_key_release_event_t *event); diff --git a/src/plugins/platforms/xcb/qxcbmain.cpp b/src/plugins/platforms/xcb/qxcbmain.cpp index 539d033ca9..c1e37f3704 100644 --- a/src/plugins/platforms/xcb/qxcbmain.cpp +++ b/src/plugins/platforms/xcb/qxcbmain.cpp @@ -53,7 +53,7 @@ public: QPlatformIntegration* QXcbIntegrationPlugin::create(const QString& system, const QStringList& parameters, int &argc, char **argv) { if (!system.compare(QLatin1String("xcb"), Qt::CaseInsensitive)) { - QXcbIntegration *xcbIntegration = new QXcbIntegration(parameters, argc, argv); + auto xcbIntegration = new QXcbIntegration(parameters, argc, argv); if (!xcbIntegration->hasDefaultConnection()) { delete xcbIntegration; return nullptr; diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp index 98bedea48a..524af5a2a7 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp @@ -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) diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h index d1f2747bea..4656f46be5 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.h +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.h @@ -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; diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp index 8c0ce8dd7e..35e90e4206 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.cpp +++ b/src/plugins/platforms/xcb/qxcbscreen.cpp @@ -747,7 +747,11 @@ void QXcbScreen::updateGeometry(const QRect &geometry, uint8_t rotation) m_sizeMillimeters = sizeInMillimeters(geometry.size(), m_virtualDesktop->dpi()); qreal dpi = geometry.width() / physicalSize().width() * qreal(25.4); - m_pixelDensity = qMax(1, qRound(dpi/96)); + + // Use 128 as a reference DPI on small screens. This favors "small UI" over "large UI". + qreal referenceDpi = physicalSize().width() <= 320 ? 128 : 96; + + m_pixelDensity = qMax(1, qRound(dpi/referenceDpi)); m_geometry = geometry; m_availableGeometry = geometry & m_virtualDesktop->workArea(); QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), m_geometry, m_availableGeometry); diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index 69fc6c2951..e56f6b13d8 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -118,8 +118,6 @@ enum { defaultWindowHeight = 160 }; -//#ifdef NET_WM_STATE_DEBUG - QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(xcb_rectangle_t, Q_PRIMITIVE_TYPE); @@ -499,12 +497,10 @@ void QXcbWindow::create() clientMachine.size(), clientMachine.constData()); } + // Create WM_HINTS property on the window, so we can xcb_get_wm_hints*() + // from various setter functions for adjusting the hints. xcb_wm_hints_t hints; memset(&hints, 0, sizeof(hints)); - xcb_wm_hints_set_normal(&hints); - - xcb_wm_hints_set_input(&hints, !(window()->flags() & Qt::WindowDoesNotAcceptFocus)); - xcb_set_wm_hints(xcb_connection(), m_window, &hints); xcb_window_t leader = connection()->clientLeader(); @@ -532,9 +528,6 @@ void QXcbWindow::create() setWindowFlags(window()->flags()); setWindowTitle(window()->title()); - if (window()->flags() & Qt::WindowTransparentForInput) - setTransparentForMouseEvents(true); - #if QT_CONFIG(xcb_xlib) // force sync to read outstanding requests - see QTBUG-29106 XSync(static_cast<Display*>(platformScreen->connection()->xlib_display()), false); @@ -739,20 +732,6 @@ void QXcbWindow::show() { if (window()->isTopLevel()) { - xcb_get_property_cookie_t cookie = xcb_get_wm_hints_unchecked(xcb_connection(), m_window); - - xcb_wm_hints_t hints; - xcb_get_wm_hints_reply(xcb_connection(), cookie, &hints, NULL); - - if (window()->windowStates() & Qt::WindowMinimized) - xcb_wm_hints_set_iconic(&hints); - else - xcb_wm_hints_set_normal(&hints); - - xcb_wm_hints_set_input(&hints, !(window()->flags() & Qt::WindowDoesNotAcceptFocus)); - - xcb_set_wm_hints(xcb_connection(), m_window, &hints); - // update WM_NORMAL_HINTS propagateSizeHints(); @@ -775,9 +754,6 @@ void QXcbWindow::show() if (!transientXcbParent) xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR); - // update _MOTIF_WM_HINTS - updateMotifWmHintsBeforeMap(); - // update _NET_WM_STATE updateNetWmStateBeforeMap(); } @@ -928,8 +904,6 @@ void QXcbWindow::doFocusOut() struct QtMotifWmHints { quint32 flags, functions, decorations; - qint32 input_mode; - quint32 status; }; enum { @@ -951,50 +925,8 @@ enum { MWM_DECOR_MENU = (1L << 4), MWM_DECOR_MINIMIZE = (1L << 5), MWM_DECOR_MAXIMIZE = (1L << 6), - - MWM_HINTS_INPUT_MODE = (1L << 2), - - MWM_INPUT_MODELESS = 0L, - MWM_INPUT_PRIMARY_APPLICATION_MODAL = 1L, - MWM_INPUT_FULL_APPLICATION_MODAL = 3L }; -static QtMotifWmHints getMotifWmHints(QXcbConnection *c, xcb_window_t window) -{ - QtMotifWmHints hints; - - auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, c->xcb_connection(), 0, window, - c->atom(QXcbAtom::_MOTIF_WM_HINTS), c->atom(QXcbAtom::_MOTIF_WM_HINTS), 0, 20); - - if (reply && reply->format == 32 && reply->type == c->atom(QXcbAtom::_MOTIF_WM_HINTS)) { - hints = *((QtMotifWmHints *)xcb_get_property_value(reply.get())); - } else { - hints.flags = 0L; - hints.functions = MWM_FUNC_ALL; - hints.decorations = MWM_DECOR_ALL; - hints.input_mode = 0L; - hints.status = 0L; - } - - return hints; -} - -static void setMotifWmHints(QXcbConnection *c, xcb_window_t window, const QtMotifWmHints &hints) -{ - if (hints.flags != 0l) { - xcb_change_property(c->xcb_connection(), - XCB_PROP_MODE_REPLACE, - window, - c->atom(QXcbAtom::_MOTIF_WM_HINTS), - c->atom(QXcbAtom::_MOTIF_WM_HINTS), - 32, - 5, - &hints); - } else { - xcb_delete_property(c->xcb_connection(), window, c->atom(QXcbAtom::_MOTIF_WM_HINTS)); - } -} - QXcbWindow::NetWmStates QXcbWindow::netWmStates() { NetWmStates result(0); @@ -1023,9 +955,7 @@ QXcbWindow::NetWmStates QXcbWindow::netWmStates() if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION))) result |= NetWmStateDemandsAttention; } else { -#ifdef NET_WM_STATE_DEBUG - printf("getting net wm state (%x), empty\n", m_window); -#endif + qCDebug(lcQpaXcb, "getting net wm state (%x), empty\n", m_window); } return result; @@ -1098,22 +1028,18 @@ void QXcbWindow::setWindowFlags(Qt::WindowFlags flags) setWmWindowType(wmWindowTypes, flags); setNetWmStateWindowFlags(flags); - setMotifWindowFlags(flags); + setMotifWmHints(flags); setTransparentForMouseEvents(flags & Qt::WindowTransparentForInput); updateDoesNotAcceptFocus(flags & Qt::WindowDoesNotAcceptFocus); } -void QXcbWindow::setMotifWindowFlags(Qt::WindowFlags flags) +void QXcbWindow::setMotifWmHints(Qt::WindowFlags flags) { Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); QtMotifWmHints mwmhints; - mwmhints.flags = 0L; - mwmhints.functions = 0L; - mwmhints.decorations = 0; - mwmhints.input_mode = 0L; - mwmhints.status = 0L; + memset(&mwmhints, 0, sizeof(mwmhints)); if (type != Qt::SplashScreen) { mwmhints.flags |= MWM_HINTS_DECORATIONS; @@ -1171,7 +1097,18 @@ void QXcbWindow::setMotifWindowFlags(Qt::WindowFlags flags) mwmhints.decorations = 0; } - setMotifWmHints(connection(), m_window, mwmhints); + if (mwmhints.flags) { + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_window, + atom(QXcbAtom::_MOTIF_WM_HINTS), + atom(QXcbAtom::_MOTIF_WM_HINTS), + 32, + 5, + &mwmhints); + } else { + xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_MOTIF_WM_HINTS)); + } } void QXcbWindow::changeNetWmState(bool set, xcb_atom_t one, xcb_atom_t two) @@ -1230,64 +1167,18 @@ void QXcbWindow::setWindowState(Qt::WindowStates state) changeNetWmState(state & Qt::WindowFullScreen, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)); } - connection()->sync(); - m_windowState = state; -} - -void QXcbWindow::updateMotifWmHintsBeforeMap() -{ - QtMotifWmHints mwmhints = getMotifWmHints(connection(), m_window); - - if (window()->modality() != Qt::NonModal) { - switch (window()->modality()) { - case Qt::WindowModal: - mwmhints.input_mode = MWM_INPUT_PRIMARY_APPLICATION_MODAL; - break; - case Qt::ApplicationModal: - default: - mwmhints.input_mode = MWM_INPUT_FULL_APPLICATION_MODAL; - break; - } - mwmhints.flags |= MWM_HINTS_INPUT_MODE; - } else { - mwmhints.input_mode = MWM_INPUT_MODELESS; - mwmhints.flags &= ~MWM_HINTS_INPUT_MODE; - } - - if (windowMinimumSize() == windowMaximumSize()) { - // fixed size, remove the resize handle (since mwm/dtwm - // isn't smart enough to do it itself) - mwmhints.flags |= MWM_HINTS_FUNCTIONS; - if (mwmhints.functions == MWM_FUNC_ALL) { - mwmhints.functions = MWM_FUNC_MOVE; - } else { - mwmhints.functions &= ~MWM_FUNC_RESIZE; - } - - if (mwmhints.decorations == MWM_DECOR_ALL) { - mwmhints.flags |= MWM_HINTS_DECORATIONS; - mwmhints.decorations = (MWM_DECOR_BORDER - | MWM_DECOR_TITLE - | MWM_DECOR_MENU); - } else { - mwmhints.decorations &= ~MWM_DECOR_RESIZEH; - } - } - - if (window()->flags() & Qt::WindowMinimizeButtonHint) { - mwmhints.flags |= MWM_HINTS_DECORATIONS; - mwmhints.decorations |= MWM_DECOR_MINIMIZE; - mwmhints.functions |= MWM_FUNC_MINIMIZE; - } - if (window()->flags() & Qt::WindowMaximizeButtonHint) { - mwmhints.flags |= MWM_HINTS_DECORATIONS; - mwmhints.decorations |= MWM_DECOR_MAXIMIZE; - mwmhints.functions |= MWM_FUNC_MAXIMIZE; + xcb_get_property_cookie_t cookie = xcb_get_wm_hints_unchecked(xcb_connection(), m_window); + xcb_wm_hints_t hints; + if (xcb_get_wm_hints_reply(xcb_connection(), cookie, &hints, nullptr)) { + if (state & Qt::WindowMinimized) + xcb_wm_hints_set_iconic(&hints); + else + xcb_wm_hints_set_normal(&hints); + xcb_set_wm_hints(xcb_connection(), m_window, &hints); } - if (window()->flags() & Qt::WindowCloseButtonHint) - mwmhints.functions |= MWM_FUNC_CLOSE; - setMotifWmHints(connection(), m_window, mwmhints); + connection()->sync(); + m_windowState = state; } void QXcbWindow::updateNetWmStateBeforeMap() @@ -1402,9 +1293,8 @@ void QXcbWindow::updateDoesNotAcceptFocus(bool doesNotAcceptFocus) xcb_get_property_cookie_t cookie = xcb_get_wm_hints_unchecked(xcb_connection(), m_window); xcb_wm_hints_t hints; - if (!xcb_get_wm_hints_reply(xcb_connection(), cookie, &hints, NULL)) { + if (!xcb_get_wm_hints_reply(xcb_connection(), cookie, &hints, nullptr)) return; - } xcb_wm_hints_set_input(&hints, !doesNotAcceptFocus); xcb_set_wm_hints(xcb_connection(), m_window, &hints); @@ -1818,21 +1708,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 +2041,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; diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h index f7d76ed3b2..6667f45343 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.h +++ b/src/plugins/platforms/xcb/qxcbwindow.h @@ -199,10 +199,9 @@ protected: NetWmStates netWmStates(); void setNetWmStates(NetWmStates); - void setMotifWindowFlags(Qt::WindowFlags flags); - void setNetWmStateWindowFlags(Qt::WindowFlags flags); + void setMotifWmHints(Qt::WindowFlags flags); - void updateMotifWmHintsBeforeMap(); + void setNetWmStateWindowFlags(Qt::WindowFlags flags); void updateNetWmStateBeforeMap(); void setTransparentForMouseEvents(bool transparent); diff --git a/src/plugins/platforms/xcb/xcb-plugin.pro b/src/plugins/platforms/xcb/xcb-plugin.pro index a2c56a3dcf..4c646d42c6 100644 --- a/src/plugins/platforms/xcb/xcb-plugin.pro +++ b/src/plugins/platforms/xcb/xcb-plugin.pro @@ -8,6 +8,7 @@ macos: CONFIG += no_app_extension_api_only SOURCES = \ qxcbmain.cpp + OTHER_FILES += xcb.json README PLUGIN_TYPE = platforms diff --git a/src/plugins/platforms/xcb/xcb_qpa_lib.pro b/src/plugins/platforms/xcb/xcb_qpa_lib.pro index 9390d04983..c65b145fb6 100644 --- a/src/plugins/platforms/xcb/xcb_qpa_lib.pro +++ b/src/plugins/platforms/xcb/xcb_qpa_lib.pro @@ -5,7 +5,7 @@ DEFINES += QT_NO_FOREACH QT += \ core-private gui-private \ service_support-private theme_support-private \ - eventdispatcher_support-private fontdatabase_support-private \ + fontdatabase_support-private \ edid_support-private qtHaveModule(linuxaccessibility_support-private): \ @@ -13,6 +13,8 @@ qtHaveModule(linuxaccessibility_support-private): \ qtConfig(vulkan): QT += vulkan_support-private +qtConfig(glib) : QMAKE_USE_PRIVATE += glib + SOURCES = \ qxcbclipboard.cpp \ qxcbconnection.cpp \ @@ -27,7 +29,12 @@ SOURCES = \ qxcbcursor.cpp \ qxcbimage.cpp \ qxcbxsettings.cpp \ - qxcbsystemtraytracker.cpp + qxcbsystemtraytracker.cpp \ + qxcbeventqueue.cpp \ + qxcbeventdispatcher.cpp \ + qxcbconnection_basic.cpp \ + qxcbconnection_screens.cpp \ + qxcbatom.cpp HEADERS = \ qxcbclipboard.h \ @@ -45,7 +52,11 @@ HEADERS = \ qxcbimage.h \ qxcbxsettings.h \ qxcbsystemtraytracker.h \ - qxcbxkbcommon.h + qxcbxkbcommon.h \ + qxcbeventqueue.h \ + qxcbeventdispatcher.h \ + qxcbconnection_basic.h \ + qxcbatom.h qtConfig(draganddrop) { SOURCES += qxcbdrag.cpp diff --git a/src/plugins/platformthemes/platformthemes.pro b/src/plugins/platformthemes/platformthemes.pro index 06ffc4cc9f..3bcc659199 100644 --- a/src/plugins/platformthemes/platformthemes.pro +++ b/src/plugins/platformthemes/platformthemes.pro @@ -1,6 +1,6 @@ TEMPLATE = subdirs QT_FOR_CONFIG += widgets-private -qtConfig(dbus):qtConfig(regularexpression): SUBDIRS += xdgdesktopportal +qtConfig(dbus):qtConfig(regularexpression):qtConfig(mimetype): SUBDIRS += xdgdesktopportal qtHaveModule(widgets):qtConfig(gtk3): SUBDIRS += gtk3 diff --git a/src/plugins/printsupport/cups/qppdprintdevice.cpp b/src/plugins/printsupport/cups/qppdprintdevice.cpp index d2ddc4144f..51b93a0016 100644 --- a/src/plugins/printsupport/cups/qppdprintdevice.cpp +++ b/src/plugins/printsupport/cups/qppdprintdevice.cpp @@ -473,7 +473,7 @@ bool QPpdPrintDevice::isFeatureAvailable(QPrintDevice::PrintDevicePropertyKey ke return QPlatformPrintDevice::isFeatureAvailable(key, params); } -#ifndef QT_NO_MIMETYPE +#if QT_CONFIG(mimetype) void QPpdPrintDevice::loadMimeTypes() const { // TODO No CUPS api? Need to manually load CUPS mime.types file? diff --git a/src/plugins/printsupport/cups/qppdprintdevice.h b/src/plugins/printsupport/cups/qppdprintdevice.h index 90f90d6788..3baf8b771b 100644 --- a/src/plugins/printsupport/cups/qppdprintdevice.h +++ b/src/plugins/printsupport/cups/qppdprintdevice.h @@ -99,7 +99,7 @@ protected: void loadOutputBins() const override; void loadDuplexModes() const override; void loadColorModes() const override; -#ifndef QT_NO_MIMETYPE +#if QT_CONFIG(mimetype) void loadMimeTypes() const override; #endif diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index 2a21673054..94d048ca7e 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -238,6 +238,33 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QVerticalSplitView); } @end +// See render code in drawPrimitive(PE_FrameTabWidget) +@interface QT_MANGLE_NAMESPACE(QDarkNSBox) : NSBox +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QDarkNSBox); + +@implementation QDarkNSBox +- (instancetype)init +{ + if ((self = [super init])) { + self.title = @""; + self.titlePosition = NSNoTitle; + self.boxType = NSBoxCustom; + self.cornerRadius = 3; + self.borderColor = [NSColor.controlColor colorWithAlphaComponent:0.1]; + self.fillColor = [NSColor.darkGrayColor colorWithAlphaComponent:0.2]; + } + + return self; +} + +- (void)drawRect:(NSRect)rect +{ + [super drawRect:rect]; +} +@end + QT_BEGIN_NAMESPACE // The following constants are used for adjusting the size @@ -1468,8 +1495,8 @@ QRectF QMacStylePrivate::CocoaControl::adjustedControlFrame(const QRectF &rect) QRectF frameRect; const auto frameSize = defaultFrameSize(); if (type == QMacStylePrivate::Button_SquareButton) { - frameRect = rect.adjusted(3, 1, -3, -5) - .adjusted(focusRingWidth, focusRingWidth, -focusRingWidth, -focusRingWidth); + frameRect = rect.adjusted(3, 1, -3, -1) + .adjusted(focusRingWidth, focusRingWidth, -focusRingWidth, -focusRingWidth); } else if (type == QMacStylePrivate::Button_PushButton) { // Start from the style option's top-left corner. frameRect = QRectF(rect.topLeft(), @@ -1704,18 +1731,28 @@ NSView *QMacStylePrivate::cocoaControl(CocoaControl widget) const || widget.size == QStyleHelper::SizeDefault) return nil; + if (widget.type == Box) { + if (__builtin_available(macOS 10.14, *)) { + if (qt_mac_applicationIsInDarkMode()) { + // See render code in drawPrimitive(PE_FrameTabWidget) + widget.type = Box_Dark; + } + } + } + NSView *bv = cocoaControls.value(widget, nil); if (!bv) { switch (widget.type) { case Box: { - NSBox *bc = [[NSBox alloc] init]; - bc.title = @""; - bc.titlePosition = NSNoTitle; - bc.boxType = NSBoxPrimary; - bc.borderType = NSBezelBorder; - bv = bc; + NSBox *box = [[NSBox alloc] init]; + bv = box; + box.title = @""; + box.titlePosition = NSNoTitle; break; } + case Box_Dark: + bv = [[QDarkNSBox alloc] init]; + break; case Button_CheckBox: case Button_Disclosure: case Button_PushButton: @@ -2922,10 +2959,28 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai { const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Box, QStyleHelper::SizeLarge); auto *box = static_cast<NSBox *>(d->cocoaControl(cw)); + // FIXME Since macOS 10.14, simply calling drawRect: won't display anything anymore. + // The AppKit team is aware of this and has proposed a couple of solutions. + // The first solution was to call displayRectIgnoringOpacity:inContext: instead. + // However, it doesn't seem to work on 10.13. More importantly, dark mode on 10.14 + // is extremely slow. Light mode works fine. + // The second solution is to subclass NSBox and reimplement a trivial drawRect: which + // would only call super. This works without any issue on 10.13, but a double border + // shows on 10.14 in both light and dark modes. + // The code below picks what works on each version and mode. On 10.13 and earlier, we + // simply call drawRect: on a regular NSBox. On 10.14, we call displayRectIgnoringOpacity: + // inContext:, but only in light mode. In dark mode, we use a custom NSBox subclass, + // QDarkNSBox, of type NSBoxCustom. Its appearance is close enough to the real thing so + // we can use this for now. d->drawNSViewInRect(box, opt->rect, p, ^(CGContextRef ctx, const CGRect &rect) { CGContextTranslateCTM(ctx, 0, rect.origin.y + rect.size.height); CGContextScaleCTM(ctx, 1, -1); - [box drawRect:rect]; + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSMojave + || [box isMemberOfClass:QDarkNSBox.class]) { + [box drawRect:rect]; + } else { + [box displayRectIgnoringOpacity:box.bounds inContext:NSGraphicsContext.currentContext]; + } }); break; } diff --git a/src/plugins/styles/mac/qmacstyle_mac_p_p.h b/src/plugins/styles/mac/qmacstyle_mac_p_p.h index 8c712e838a..dd99cf4bb5 100644 --- a/src/plugins/styles/mac/qmacstyle_mac_p_p.h +++ b/src/plugins/styles/mac/qmacstyle_mac_p_p.h @@ -187,6 +187,7 @@ public: enum CocoaControlType { NoControl, // For when there's no such a control in Cocoa Box, // QGroupBox + Box_Dark, // FIXME See render code in drawPrimitive(PE_FrameTabWidget) Button_CheckBox, Button_Disclosure, // Disclosure triangle, like in QTreeView Button_PopupButton, // Non-editable QComboBox |