diff options
author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2023-06-09 17:30:54 +0300 |
---|---|---|
committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2023-06-09 17:30:54 +0300 |
commit | da6e958319e95fe564d3b30c931492dd666bfaff (patch) | |
tree | 16ac1556a573daeba5c9c4b795f86aa166ffe467 /src/plugins/platforms | |
parent | 29400a683f96867133b28299c0d0bd6bcf40df35 (diff) | |
parent | a96fc76fa78f3500266b3a34016f9e1bd29b319c (diff) |
Merge remote-tracking branch 'origin/tqtc/lts-5.15.11' into tqtc/lts-5.15-opensourcev5.15.11-lts-lgpl
Change-Id: Iac056a5e9f59fc8d1929171c18039aeb45be22b8
Diffstat (limited to 'src/plugins/platforms')
28 files changed, 300 insertions, 104 deletions
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index b5783bb67a..0f0cd1e16f 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -96,7 +96,13 @@ namespace QtAndroidAccessibility template <typename Func, typename Ret> void runInObjectContext(QObject *context, Func &&func, Ret *retVal) { - QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal); + if (!QtAndroid::blockEventLoopsWhenSuspended() + || QGuiApplication::applicationState() != Qt::ApplicationSuspended) { + QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal); + } else { + __android_log_print(ANDROID_LOG_WARN, m_qtTag, + "Could not run accessibility call in object context, event loop suspended."); + } } void initialize() @@ -160,6 +166,11 @@ namespace QtAndroidAccessibility QtAndroid::notifyValueChanged(accessibilityObjectId, value); } + void notifyScrolledEvent(uint accessiblityObjectId) + { + QtAndroid::notifyScrolledEvent(accessiblityObjectId); + } + static QVarLengthArray<int, 8> childIdListForAccessibleObject_helper(int objectId) { QAccessibleInterface *iface = interfaceFromId(objectId); @@ -217,7 +228,7 @@ namespace QtAndroidAccessibility return result; } - static QRect screenRect_helper(int objectId) + static QRect screenRect_helper(int objectId, bool clip = true) { QRect rect; QAccessibleInterface *iface = interfaceFromId(objectId); @@ -225,7 +236,7 @@ namespace QtAndroidAccessibility rect = QHighDpi::toNativePixels(iface->rect(), iface->window()); } // If the widget is not fully in-bound in its parent then we have to clip the rectangle to draw - if (iface && iface->parent() && iface->parent()->isValid()) { + if (clip && iface && iface->parent() && iface->parent()->isValid()) { const auto parentRect = QHighDpi::toNativePixels(iface->parent()->rect(), iface->parent()->window()); rect = rect.intersected(parentRect); } @@ -327,23 +338,43 @@ namespace QtAndroidAccessibility static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) { bool result = false; + + const auto& ids = childIdListForAccessibleObject_helper(objectId); + if (ids.isEmpty()) + return false; + + const int firstChildId = ids.first(); + const QRect oldPosition = screenRect_helper(firstChildId, false); + if (m_accessibilityContext) { runInObjectContext(m_accessibilityContext, [objectId]() { return scroll_helper(objectId, QAccessibleActionInterface::increaseAction()); }, &result); } - return result; + + // Don't check for position change if the call was not successful + return result && oldPosition != screenRect_helper(firstChildId, false); } static jboolean scrollBackward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) { bool result = false; + + const auto& ids = childIdListForAccessibleObject_helper(objectId); + if (ids.isEmpty()) + return false; + + const int firstChildId = ids.first(); + const QRect oldPosition = screenRect_helper(firstChildId, false); + if (m_accessibilityContext) { runInObjectContext(m_accessibilityContext, [objectId]() { return scroll_helper(objectId, QAccessibleActionInterface::decreaseAction()); }, &result); } - return result; + + // Don't check for position change if the call was not successful + return result && oldPosition != screenRect_helper(firstChildId, false); } diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h index 99d151bc3f..212131ff62 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.h +++ b/src/plugins/platforms/android/androidjniaccessibility.h @@ -55,6 +55,7 @@ namespace QtAndroidAccessibility void notifyObjectHide(uint accessibilityObjectId); void notifyObjectFocus(uint accessibilityObjectId); void notifyValueChanged(uint accessibilityObjectId); + void notifyScrolledEvent(uint accessibilityObjectId); void createAccessibilityContextObject(QObject *parent); } diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 1abd23485e..bb77752734 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -121,6 +121,11 @@ namespace QtAndroidInput return m_softwareKeyboardRect; } + int getSelectHandleWidth() + { + return QJNIObjectPrivate::callStaticMethod<jint>(applicationClass(), "getSelectHandleWidth"); + } + void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl) { QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "updateHandles", "(IIIIIIIIZ)V", diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index cc3070c4aa..4b2aef07ca 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -60,6 +60,8 @@ namespace QtAndroidInput // cursor/selection handles void updateHandles(int handleCount, QPoint editMenuPos = QPoint(), uint32_t editButtons = 0, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); + int getSelectHandleWidth(); + bool registerNatives(JNIEnv *env); } diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index ad141a925c..577a7905b9 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -250,6 +250,12 @@ namespace QtAndroid value); } + void notifyScrolledEvent(uint accessibilityObjectId) + { + QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyScrolledEvent", "(I)V", + accessibilityObjectId); + } + void notifyQtAndroidPluginRunning(bool running) { QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running); @@ -677,30 +683,23 @@ static void setDisplayMetrics(JNIEnv */*env*/, jclass /*clazz*/, jdouble xdpi, jdouble ydpi, jdouble scaledDensity, jdouble density, jfloat refreshRate) { - // Android does not give us the correct screen size for immersive mode, but - // the surface does have the right size - - widthPixels = qMax(widthPixels, desktopWidthPixels); - heightPixels = qMax(heightPixels, desktopHeightPixels); - m_desktopWidthPixels = desktopWidthPixels; m_desktopHeightPixels = desktopHeightPixels; m_scaledDensity = scaledDensity; m_density = density; + const QSize screenSize(widthPixels, heightPixels); + // available geometry always starts from top left + const QRect availableGeometry(0, 0, desktopWidthPixels, desktopHeightPixels); + const QSize physicalSize(qRound(double(widthPixels) / xdpi * 25.4), + qRound(double(heightPixels) / ydpi * 25.4)); + QMutexLocker lock(&m_platformMutex); if (!m_androidPlatformIntegration) { - QAndroidPlatformIntegration::setDefaultDisplayMetrics(desktopWidthPixels, - desktopHeightPixels, - qRound(double(widthPixels) / xdpi * 25.4), - qRound(double(heightPixels) / ydpi * 25.4), - widthPixels, - heightPixels); + QAndroidPlatformIntegration::setDefaultDisplayMetrics( + availableGeometry.width(), availableGeometry.height(), physicalSize.width(), + physicalSize.height(), screenSize.width(), screenSize.height()); } else { - const QSize physicalSize(qRound(double(widthPixels) / xdpi * 25.4), - qRound(double(heightPixels) / ydpi * 25.4)); - const QSize screenSize(widthPixels, heightPixels); - const QRect availableGeometry(0, 0, desktopWidthPixels, desktopHeightPixels); m_androidPlatformIntegration->setScreenSizeParameters(physicalSize, screenSize, availableGeometry); m_androidPlatformIntegration->setRefreshRate(refreshRate); diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 9aaaa667c2..cc2839c20e 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -99,6 +99,7 @@ namespace QtAndroid void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId); void notifyObjectFocus(uint accessibilityObjectId); void notifyValueChanged(uint accessibilityObjectId, jstring value); + void notifyScrolledEvent(uint accessibilityObjectId); void notifyQtAndroidPluginRunning(bool running); const char *classErrorMsgFmt(); diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp index 07776a4a76..316859b2a6 100644 --- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp +++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp @@ -86,6 +86,7 @@ struct AssetItem { } Type type = Type::File; QString name; + qint64 size = -1; }; using AssetItemList = QVector<AssetItem>; @@ -261,7 +262,7 @@ public: bool open(QIODevice::OpenMode openMode) override { - if (m_isFolder || (openMode & QIODevice::WriteOnly)) + if (!m_assetInfo || m_assetInfo->type != AssetItem::Type::File || (openMode & QIODevice::WriteOnly)) return false; close(); m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER); @@ -275,14 +276,13 @@ public: m_assetFile = 0; return true; } - m_isFolder = false; return false; } qint64 size() const override { - if (m_assetFile) - return AAsset_getLength(m_assetFile); + if (m_assetInfo) + return m_assetInfo->size; return -1; } @@ -326,10 +326,12 @@ public: { FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag); FileFlags flags; - if (m_assetFile) - flags = FileType | commonFlags; - else if (m_isFolder) - flags = DirectoryType | commonFlags; + if (m_assetInfo) { + if (m_assetInfo->type == AssetItem::Type::File) + flags = FileType | commonFlags; + else if (m_assetInfo->type == AssetItem::Type::Folder) + flags = DirectoryType | commonFlags; + } return type & flags; } @@ -364,21 +366,43 @@ public: return; close(); m_fileName = cleanedAssetPath(file); - switch (FolderIterator::fileType(m_fileName)) { - case AssetItem::Type::File: - open(QIODevice::ReadOnly); - break; - case AssetItem::Type::Folder: - m_isFolder = true; - break; - case AssetItem::Type::Invalid: - break; + + { + QMutexLocker lock(&m_assetsInfoCacheMutex); + QSharedPointer<AssetItem> *assetInfoPtr = m_assetsInfoCache.object(m_fileName); + if (assetInfoPtr) { + m_assetInfo = *assetInfoPtr; + return; + } } + + QSharedPointer<AssetItem> *newAssetInfoPtr = new QSharedPointer<AssetItem>(new AssetItem); + + m_assetInfo = *newAssetInfoPtr; + m_assetInfo->name = m_fileName; + m_assetInfo->type = AssetItem::Type::Invalid; + + m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER); + + if (m_assetFile) { + m_assetInfo->type = AssetItem::Type::File; + m_assetInfo->size = AAsset_getLength(m_assetFile); + } else { + auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8()); + if (assetDir) { + if (AAssetDir_getNextFileName(assetDir)) + m_assetInfo->type = AssetItem::Type::Folder; + AAssetDir_close(assetDir); + } + } + + QMutexLocker lock(&m_assetsInfoCacheMutex); + m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr); } Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override { - if (m_isFolder) + if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder) return new AndroidAbstractFileEngineIterator(filters, filterNames, m_fileName); return nullptr; } @@ -388,9 +412,14 @@ private: AAssetManager *m_assetManager = nullptr; // initialize with a name that can't be used as a file name QString m_fileName = QLatin1String("."); - bool m_isFolder = false; + QSharedPointer<AssetItem> m_assetInfo; + + static QCache<QString, QSharedPointer<AssetItem>> m_assetsInfoCache; + static QMutex m_assetsInfoCacheMutex; }; +QCache<QString, QSharedPointer<AssetItem>> AndroidAbstractFileEngine::m_assetsInfoCache(std::max(200, qEnvironmentVariableIntValue("QT_ANDROID_MAX_FILEINFO_ASSETS_CACHE_SIZE"))); +QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex; AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler() { diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 3b7f29ec1a..8a44482d44 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -514,7 +514,12 @@ QAndroidInputContext::QAndroidInputContext() auto im = qGuiApp->inputMethod(); if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) || !im->inputItemClipRectangle().contains(im->cursorRectangle())) { - m_handleMode = Hidden; + // Undoes the hidden request if the only reason for the hidden is that + // X of the cursorRectangle or X of the anchorRectangle is less than 0. + const int rectX = im->inputItemClipRectangle().x(); + if (im->cursorRectangle().x() > rectX && im->anchorRectangle().x() > rectX) + m_handleMode = Hidden; + updateSelectionHandles(); } }); @@ -623,13 +628,13 @@ void QAndroidInputContext::updateSelectionHandles() if (noHandles) return; + QWindow *window = qGuiApp->focusWindow(); auto im = qGuiApp->inputMethod(); - if (!m_focusObject || ((m_handleMode & 0xff) == Hidden)) { + if (!m_focusObject || ((m_handleMode & 0xff) == Hidden) || !window) { // Hide the handles QtAndroidInput::updateHandles(Hidden); return; } - QWindow *window = qGuiApp->focusWindow(); double pixelDensity = window ? QHighDpiScaling::factor(window) : QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen()); @@ -647,14 +652,25 @@ void QAndroidInputContext::updateSelectionHandles() } auto curRect = im->cursorRectangle(); - QPoint cursorPoint(window->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()))); - QPoint editMenuPoint(cursorPoint.x(), cursorPoint.y()); + QPoint cursorPointGlobal = window->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height())); + QPoint cursorPoint(curRect.center().x(), curRect.bottom()); + int x = curRect.x(); + int y = curRect.y(); + + // Use x and y for the editMenuPoint from the cursorPointGlobal when the cursor is in the Dialog + if (cursorPointGlobal != cursorPoint) { + x = cursorPointGlobal.x(); + y = cursorPointGlobal.y(); + } + + QPoint editMenuPoint(x, y); m_handleMode &= ShowEditPopup; m_handleMode |= ShowCursor; uint32_t buttons = EditContext::PasteButton; if (!query.value(Qt::ImSurroundingText).toString().isEmpty()) buttons |= EditContext::SelectAllButton; - QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons, cursorPoint * pixelDensity); + QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons, + cursorPointGlobal * pixelDensity); // The VK is hidden, reset the timer if (m_hideCursorHandleTimer.isActive()) m_hideCursorHandleTimer.start(); @@ -667,12 +683,30 @@ void QAndroidInputContext::updateSelectionHandles() if (cpos > anchor) std::swap(leftRect, rightRect); + // Move the left or right select handle to the center from the screen edge + // the select handle is close to or over the screen edge. Otherwise, the + // select handle might go out of the screen and it would be impossible to drag. QPoint leftPoint(window->mapToGlobal(leftRect.bottomLeft().toPoint())); - QPoint righPoint(window->mapToGlobal(rightRect.bottomRight().toPoint())); - QPoint editPoint(window->mapToGlobal(leftRect.united(rightRect) - .topLeft().toPoint())); + QPoint rightPoint(window->mapToGlobal(rightRect.bottomRight().toPoint())); + static int m_selectHandleWidth = 0; + // For comparison, get the width of the handle. + // Only half of the width will protrude from the cursor on each side + if (m_selectHandleWidth == 0) + m_selectHandleWidth = QtAndroidInput::getSelectHandleWidth() / 2; + + int rightSideOfScreen = QtAndroid::androidPlatformIntegration()->screen()->availableGeometry().right(); + + // Check if handle will fit the screen on left side. If not, then move it closer to the center + if (leftPoint.x() <= m_selectHandleWidth) + leftPoint.setX(m_selectHandleWidth / pixelDensity); + + // Check if handle will fit the screen on right side. If not, then move it closer to the center + if (rightPoint.x() >= (rightSideOfScreen / pixelDensity) - m_selectHandleWidth) + rightPoint.setX((rightSideOfScreen / pixelDensity) - (m_selectHandleWidth / pixelDensity)); + + QPoint editPoint(window->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint())); QtAndroidInput::updateHandles(m_handleMode, editPoint * pixelDensity, EditContext::AllButtons, - leftPoint * pixelDensity, righPoint * pixelDensity, + leftPoint * pixelDensity, rightPoint * pixelDensity, query.value(Qt::ImCurrentSelection).toString().isRightToLeft()); m_hideCursorHandleTimer.stop(); } diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp index bdbf709b3d..b29190d3e9 100644 --- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp +++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp @@ -68,6 +68,8 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent * QtAndroidAccessibility::notifyObjectFocus(event->uniqueId()); } else if (event->type() == QAccessible::ValueChanged) { QtAndroidAccessibility::notifyValueChanged(event->uniqueId()); + } else if (event->type() == QAccessible::ScrollingEnd) { + QtAndroidAccessibility::notifyScrolledEvent(event->uniqueId()); } } diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index 4bd1b129bd..2af9b8f556 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -221,6 +221,12 @@ bool QCocoaDrag::maybeDragMultipleItems() // contains a combined picture for all urls we drag. auto imageOrNil = dragImage; for (const auto &qtUrl : qtUrls) { + if (!qtUrl.isValid()) + continue; + + if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items. + continue; + NSURL *nsUrl = qtUrl.toNSURL(); auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease]; const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y, diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h index 93dff027a6..448b281665 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.h +++ b/src/plugins/platforms/cocoa/qcocoascreen.h @@ -65,8 +65,8 @@ public: QImage::Format format() const override { return m_format; } qreal devicePixelRatio() const override { return m_devicePixelRatio; } QSizeF physicalSize() const override { return m_physicalSize; } - QDpi logicalDpi() const override { return m_logicalDpi; } - QDpi logicalBaseDpi() const override { return m_logicalDpi; } + QDpi logicalDpi() const override { return QDpi(72, 72); } + QDpi logicalBaseDpi() const override { return QDpi(72, 72); } qreal refreshRate() const override { return m_refreshRate; } QString name() const override { return m_name; } QPlatformCursor *cursor() const override { return m_cursor; } @@ -113,7 +113,6 @@ private: QRect m_geometry; QRect m_availableGeometry; - QDpi m_logicalDpi; qreal m_refreshRate = 0; int m_depth = 0; QString m_name; diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index 1c2daef392..203df61d82 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -106,6 +106,18 @@ void QCocoaScreen::initializeScreens() */ void QCocoaScreen::updateScreens() { + // Adding, updating, or removing a screen below might trigger + // Qt or the application to move a window to a different screen, + // recursing back here via QCocoaWindow::windowDidChangeScreen. + // The update code is not re-entrant, so bail out if we end up + // in this situation. The screens will stabilize eventually. + static bool updatingScreens = false; + if (updatingScreens) { + qCInfo(lcQpaScreen) << "Skipping screen update, already updating"; + return; + } + QBoolBlocker recursionGuard(updatingScreens); + uint32_t displayCount = 0; if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess) qFatal("Failed to get number of online displays"); @@ -267,7 +279,6 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) const QRect previousGeometry = m_geometry; const QRect previousAvailableGeometry = m_availableGeometry; - const QDpi previousLogicalDpi = m_logicalDpi; const qreal previousRefreshRate = m_refreshRate; // The reference screen for the geometry is always the primary screen @@ -282,8 +293,6 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) CGSize size = CGDisplayScreenSize(m_displayId); m_physicalSize = QSizeF(size.width, size.height); - m_logicalDpi.first = 72; - m_logicalDpi.second = 72; QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId); float refresh = CGDisplayModeGetRefreshRate(displayMode); @@ -295,8 +304,6 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) if (didChangeGeometry) QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); - if (m_logicalDpi != previousLogicalDpi) - QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); if (m_refreshRate != previousRefreshRate) QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); } @@ -309,6 +316,11 @@ void QCocoaScreen::requestUpdate() { Q_ASSERT(m_displayId); + if (!isOnline()) { + qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request"; + return; + } + if (!m_displayLink) { CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink); CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*, diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index d73b028afb..b6ab9c0bbc 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -465,11 +465,11 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const if (iconType != 0) { QPixmap pixmap; IconRef icon = nullptr; - GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon); + QT_IGNORE_DEPRECATIONS(GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon)); if (icon) { pixmap = qt_mac_convert_iconref(icon, size.width(), size.height()); - ReleaseIconRef(icon); + QT_IGNORE_DEPRECATIONS(ReleaseIconRef(icon)); } return pixmap; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 932e4a3af9..6bfdd82e19 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -687,9 +687,10 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) switch (currentState) { case Qt::WindowMinimized: [nsWindow deminiaturize:sender]; - Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", - "[NSWindow deminiaturize:] is synchronous"); - break; + // Deminiaturizing is not synchronous, so we need to wait for the + // NSWindowDidMiniaturizeNotification before continuing to apply + // the new state. + return; case Qt::WindowFullScreen: { toggleFullScreen(); // Exiting fullscreen is not synchronous, so we need to wait for the @@ -853,7 +854,15 @@ void QCocoaWindow::windowDidDeminiaturize() if (!isContentView()) return; + Qt::WindowState requestedState = window()->windowState(); + handleWindowStateChanged(); + + if (requestedState != windowState() && requestedState != Qt::WindowMinimized) { + // We were only going out of minimized as an intermediate step before + // progressing into the final step, so re-sync the desired state. + applyWindowState(requestedState); + } } void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp index b35935bb5b..a3d6df7d11 100644 --- a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp @@ -343,8 +343,7 @@ void QEglFSCursor::paintOnScreen() // screens are siblings of each other. When not enabled, the sibling list // only contains m_screen itself. for (QPlatformScreen *screen : m_screen->virtualSiblings()) { - if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot) - && QOpenGLContext::currentContext()->screen() == screen->screen()) + if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot)) { cr.translate(-screen->geometry().topLeft()); const QSize screenSize = screen->geometry().size(); @@ -468,11 +467,12 @@ void QEglFSCursor::draw(const QRectF &r) { StateSaver stateSaver; - QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; - if (!gfx.program) { - // one time initialization + // one time initialization + if (!QOpenGLFunctions::d_ptr) initializeOpenGLFunctions(); + QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; + if (!gfx.program) { createShaderPrograms(); if (!gfx.atlasTexture) { diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index 95b51c9601..33d144807f 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -359,20 +359,21 @@ void QEglFSKmsGbmScreen::flip() if (d.screen != this) { d.screen->ensureModeSet(fb->fb); d.cloneFlipPending = true; + QKmsOutput &destOutput(d.screen->output()); if (device()->hasAtomicSupport()) { #if QT_CONFIG(drm_atomic) drmModeAtomicReq *request = device()->threadLocalAtomicRequest(); if (request) { - drmModeAtomicAddProperty(request, d.screen->output().eglfs_plane->id, - d.screen->output().eglfs_plane->framebufferPropertyId, fb->fb); - drmModeAtomicAddProperty(request, d.screen->output().eglfs_plane->id, - d.screen->output().eglfs_plane->crtcPropertyId, op.crtc_id); + drmModeAtomicAddProperty(request, destOutput.eglfs_plane->id, + destOutput.eglfs_plane->framebufferPropertyId, fb->fb); + drmModeAtomicAddProperty(request, destOutput.eglfs_plane->id, + destOutput.eglfs_plane->crtcPropertyId, destOutput.crtc_id); } #endif } else { int ret = drmModePageFlip(fd, - d.screen->output().crtc_id, + destOutput.crtc_id, fb->fb, DRM_MODE_PAGE_FLIP_EVENT, d.screen); diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 985eecdb1d..ac75367d7f 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -160,7 +160,7 @@ static QUIView *focusView() return; // Enable hide-keyboard gesture - self.enabled = YES; + self.enabled = m_context->isInputPanelVisible(); m_context->scrollToCursor(); } @@ -402,7 +402,7 @@ void QIOSInputContext::updateKeyboardState(NSNotification *notification) // The isInputPanelVisible() property is based on whether or not the virtual keyboard // is visible on screen, and does not follow the logic of the iOS WillShow and WillHide // notifications which are not emitted for undocked keyboards, and are buggy when dealing - // with input-accesosory-views. The reason for using frameEnd here (the future state), + // with input-accessory-views. The reason for using frameEnd here (the future state), // instead of the current state reflected in frameBegin, is that QInputMethod::isVisible() // is documented to reflect the future state in the case of animated transitions. m_keyboardState.keyboardVisible = CGRectIntersectsRect(frameEnd, [UIScreen mainScreen].bounds); @@ -727,12 +727,34 @@ bool QIOSInputContext::inputMethodAccepted() const */ void QIOSInputContext::reset() { - qImDebug("updating Qt::ImQueryAll and unmarking text"); + qImDebug("releasing text responder"); + + // UIKit will sometimes, for unknown reasons, unset the input delegate on the + // current text responder. This seems to happen as a result of us calling + // [self.inputDelegate textDidChange:self] from [m_textResponder reset]. + // But it won't be set to nil directly, only after a character is typed on + // the input panel after the reset. This strange behavior seems to be related + // to us overriding [QUIView setInteraction] to ignore UITextInteraction. If we + // didn't do that, the delegate would be kept. But not overriding that function + // has its own share of issues, so it seems better to keep that way for now. + // Instead, we choose to recreate the text responder as a brute-force solution + // until we have better knowledge of what is going on (or implement the new + // UITextInteraction protocol). + const auto oldResponder = m_textResponder; + [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)]; + [m_textResponder notifyInputDelegate:Qt::ImQueryInput]; + [m_textResponder autorelease]; + m_textResponder = nullptr; update(Qt::ImQueryAll); - [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)]; - [m_textResponder notifyInputDelegate:Qt::ImQueryInput]; + // If update() didn't end up creating a new text responder, oldResponder will still be + // the first responder. In that case we need to resign it, so that the input panel hides. + // (the input panel will apparently not hide if the first responder is only released). + if ([oldResponder isFirstResponder]) { + qImDebug("IM not enabled, resigning autoreleased text responder as first responder"); + [oldResponder resignFirstResponder]; + } } /*! diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index 6ff495ab85..9289cc68c9 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -465,7 +465,10 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>( self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); } else { - QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + // Send the touch event asynchronously, as the application might spin a recursive + // event loop in response to the touch event (a dialog e.g.), which will deadlock + // the UIKit event delivery system (QTBUG-98651). + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>( self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); } } @@ -571,7 +574,12 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime]; QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); - QWindowSystemInterface::handleTouchCancelEvent(self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); + + // Send the touch event asynchronously, as the application might spin a recursive + // event loop in response to the touch event (a dialog e.g.), which will deadlock + // the UIKit event delivery system (QTBUG-98651). + QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>( + self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); } - (int)mapPressTypeToKey:(UIPress*)press diff --git a/src/plugins/platforms/minimal/qminimalintegration.cpp b/src/plugins/platforms/minimal/qminimalintegration.cpp index 0c2c0d0b68..b69e603b01 100644 --- a/src/plugins/platforms/minimal/qminimalintegration.cpp +++ b/src/plugins/platforms/minimal/qminimalintegration.cpp @@ -42,6 +42,7 @@ #include <QtGui/private/qpixmap_raster_p.h> #include <QtGui/private/qguiapplication_p.h> +#include <qpa/qplatformnativeinterface.h> #include <qpa/qplatformwindow.h> #include <qpa/qwindowsysteminterface.h> @@ -200,6 +201,13 @@ QAbstractEventDispatcher *QMinimalIntegration::createEventDispatcher() const #endif } +QPlatformNativeInterface *QMinimalIntegration::nativeInterface() const +{ + if (!m_nativeInterface) + m_nativeInterface.reset(new QPlatformNativeInterface); + return m_nativeInterface.get(); +} + QMinimalIntegration *QMinimalIntegration::instance() { return static_cast<QMinimalIntegration *>(QGuiApplicationPrivate::platformIntegration()); diff --git a/src/plugins/platforms/minimal/qminimalintegration.h b/src/plugins/platforms/minimal/qminimalintegration.h index f9c66e0c3e..c384c28fba 100644 --- a/src/plugins/platforms/minimal/qminimalintegration.h +++ b/src/plugins/platforms/minimal/qminimalintegration.h @@ -43,6 +43,8 @@ #include <qpa/qplatformintegration.h> #include <qpa/qplatformscreen.h> +#include <qscopedpointer.h> + QT_BEGIN_NAMESPACE class QMinimalScreen : public QPlatformScreen @@ -82,12 +84,15 @@ public: QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; QAbstractEventDispatcher *createEventDispatcher() const override; + QPlatformNativeInterface *nativeInterface() const override; + unsigned options() const { return m_options; } static QMinimalIntegration *instance(); private: mutable QPlatformFontDatabase *m_fontDatabase; + mutable QScopedPointer<QPlatformNativeInterface> m_nativeInterface; QMinimalScreen *m_primaryScreen; unsigned m_options; }; diff --git a/src/plugins/platforms/vnc/qvnc.cpp b/src/plugins/platforms/vnc/qvnc.cpp index e3017a931c..5ae548bed7 100644 --- a/src/plugins/platforms/vnc/qvnc.cpp +++ b/src/plugins/platforms/vnc/qvnc.cpp @@ -671,11 +671,10 @@ void QVncServer::newConnection() void QVncServer::discardClient(QVncClient *client) { clients.removeOne(client); + qvnc_screen->disableClientCursor(client); client->deleteLater(); - if (clients.isEmpty()) { - qvnc_screen->disableClientCursor(client); + if (clients.isEmpty()) qvnc_screen->setPowerState(QPlatformScreen::PowerStateOff); - } } inline QImage QVncServer::screenImage() const diff --git a/src/plugins/platforms/vnc/qvncscreen.cpp b/src/plugins/platforms/vnc/qvncscreen.cpp index 83ef5088d0..00abbdab27 100644 --- a/src/plugins/platforms/vnc/qvncscreen.cpp +++ b/src/plugins/platforms/vnc/qvncscreen.cpp @@ -147,9 +147,10 @@ void QVncScreen::disableClientCursor(QVncClient *client) if (clientCount == 0) { delete clientCursor; clientCursor = nullptr; - } - mCursor = new QFbCursor(this); + if (mCursor == nullptr) + mCursor = new QFbCursor(this); + } #else Q_UNUSED(client) #endif diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index d5c3022080..3e0059ca90 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -821,6 +821,8 @@ static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned c if (!(cwexFlags & CWP_SKIPTRANSPARENT) && (GetWindowLongPtr(child, GWL_EXSTYLE) & WS_EX_TRANSPARENT)) { const HWND nonTransparentChild = ChildWindowFromPointEx(*hwnd, point, cwexFlags | CWP_SKIPTRANSPARENT); + if (!nonTransparentChild || nonTransparentChild == *hwnd) + return false; if (QWindowsWindow *nonTransparentWindow = context->findPlatformWindow(nonTransparentChild)) { *result = nonTransparentWindow; *hwnd = nonTransparentChild; diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp index 1afa00cfc9..118af6ce73 100644 --- a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp @@ -41,6 +41,7 @@ #include <QRect> #include <QList> +#include <QMap> #include <QDebug> #include <qmath.h> diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index 0eafb9b368..6457bc0551 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -488,12 +488,12 @@ void QXcbConnection::printXcbError(const char *message, xcb_generic_error_t *err uint clamped_error_code = qMin<uint>(error->error_code, (sizeof(xcb_errors) / sizeof(xcb_errors[0])) - 1); uint clamped_major_code = qMin<uint>(error->major_code, (sizeof(xcb_protocol_request_codes) / sizeof(xcb_protocol_request_codes[0])) - 1); - qCWarning(lcQpaXcb, "%s: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d", - message, - int(error->error_code), xcb_errors[clamped_error_code], - int(error->sequence), int(error->resource_id), - int(error->major_code), xcb_protocol_request_codes[clamped_major_code], - int(error->minor_code)); + qCDebug(lcQpaXcb, "%s: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d", + message, + int(error->error_code), xcb_errors[clamped_error_code], + int(error->sequence), int(error->resource_id), + int(error->major_code), xcb_protocol_request_codes[clamped_major_code], + int(error->minor_code)); } static Qt::MouseButtons translateMouseButtons(int s) diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index 5a36cfa0d3..1ced02f31d 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -591,8 +591,12 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail, fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y), fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event); - if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) + if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) { xi2ProcessTouch(xiDeviceEvent, platformWindow); + } else { // When the window cannot be matched, delete it from touchPoints + if (TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid)) + dev->touchPoints.remove((xiDeviceEvent->detail % INT_MAX)); + } break; } } else if (xiEnterEvent && !xi2MouseEventsDisabled() && eventListener) { diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index e0aaabbbdf..299835ba1e 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -785,7 +785,7 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff); Q_ASSERT(w); QRect geometry = w->geometry(); - p -= geometry.topLeft(); + p -= w->isEmbedded() ? w->mapToGlobal(geometry.topLeft()) : geometry.topLeft(); if (!w || !w->window() || (w->window()->type() == Qt::Desktop)) return; @@ -1041,21 +1041,30 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e Qt::DropActions supported_drop_actions; QMimeData *dropData = nullptr; + // this could be a same-application drop, just proxied due to + // some XEMBEDding, so try to find the real QMimeData used + // based on the timestamp for this drop. + int at = findTransactionByTime(target_time); + if (at != -1) { + qCDebug(lcQpaXDnd) << "found one transaction via findTransactionByTime()"; + dropData = transactions.at(at).drag->mimeData(); + // Can't use the source QMimeData if we need the image conversion code from xdndObtainData + if (dropData && dropData->hasImage()) + dropData = 0; + } + // if we can't find it, then use the data in the drag manager if (currentDrag()) { - dropData = currentDrag()->mimeData(); + if (!dropData) + dropData = currentDrag()->mimeData(); supported_drop_actions = Qt::DropActions(l[4]); } else { - dropData = m_dropData; + if (!dropData) + dropData = m_dropData; supported_drop_actions = accepted_drop_action | toDropActions(drop_actions); } if (!dropData) return; - // ### - // int at = findXdndDropTransactionByTime(target_time); - // if (at != -1) - // dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data; - // if we can't find it, then use the data in the drag manager auto buttons = currentDrag() ? b : connection()->queryMouseButtons(); auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers(); @@ -1064,7 +1073,12 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e currentWindow.data(), dropData, currentPosition, supported_drop_actions, buttons, modifiers); - setExecutedDropAction(response.acceptedAction()); + Qt::DropAction acceptedAaction = response.acceptedAction(); + if (!response.isAccepted()) { + // Ignore a failed drag + acceptedAaction = Qt::IgnoreAction; + } + setExecutedDropAction(acceptedAaction); xcb_client_message_event_t finished = {}; finished.response_type = XCB_CLIENT_MESSAGE; @@ -1074,7 +1088,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e finished.type = atom(QXcbAtom::XdndFinished); finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE; finished.data.data32[1] = response.isAccepted(); // flags - finished.data.data32[2] = toXdndAction(response.acceptedAction()); + finished.data.data32[2] = toXdndAction(acceptedAaction); qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource; @@ -1280,6 +1294,7 @@ void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event bool QXcbDrag::dndEnable(QXcbWindow *w, bool on) { + qCDebug(lcQpaXDnd) << "dndEnable" << w << on; // Windows announce that they support the XDND protocol by creating a window property XdndAware. if (on) { QXcbWindow *window = nullptr; diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp index e8286381a2..f43b2b66e1 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp +++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp @@ -441,7 +441,7 @@ static xkb_layout_index_t lockedGroup(quint16 state) void QXcbKeyboard::updateXKBStateFromCore(quint16 state) { - if (m_config && !connection()->hasXKB()) { + if (m_config) { struct xkb_state *xkbState = m_xkbState.get(); xkb_mod_mask_t modsDepressed = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_DEPRESSED); xkb_mod_mask_t modsLatched = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_LATCHED); @@ -463,7 +463,7 @@ void QXcbKeyboard::updateXKBStateFromCore(quint16 state) void QXcbKeyboard::updateXKBStateFromXI(void *modInfo, void *groupInfo) { - if (m_config && !connection()->hasXKB()) { + if (m_config) { auto *mods = static_cast<xcb_input_modifier_info_t *>(modInfo); auto *group = static_cast<xcb_input_group_info_t *>(groupInfo); const xkb_state_component changedComponents |