diff options
Diffstat (limited to 'src/plugins/platforms')
196 files changed, 16889 insertions, 5108 deletions
diff --git a/src/plugins/platforms/android/android.pro b/src/plugins/platforms/android/android.pro index 03592bfa7d..73db9e93a3 100644 --- a/src/plugins/platforms/android/android.pro +++ b/src/plugins/platforms/android/android.pro @@ -11,6 +11,8 @@ QT += \ eventdispatcher_support-private accessibility_support-private \ fontdatabase_support-private egl_support-private +qtConfig(vulkan): QT += vulkan_support-private + OTHER_FILES += $$PWD/android.json INCLUDEPATH += \ @@ -78,6 +80,13 @@ HEADERS += $$PWD/qandroidplatformintegration.h \ qtConfig(android-style-assets): SOURCES += $$PWD/extract.cpp else: SOURCES += $$PWD/extract-dummy.cpp +qtConfig(vulkan) { + SOURCES += $$PWD/qandroidplatformvulkaninstance.cpp \ + $$PWD/qandroidplatformvulkanwindow.cpp + HEADERS += $$PWD/qandroidplatformvulkaninstance.h \ + $$PWD/qandroidplatformvulkanwindow.h +} + PLUGIN_TYPE = platforms load(qt_plugin) diff --git a/src/plugins/platforms/android/androidjniclipboard.cpp b/src/plugins/platforms/android/androidjniclipboard.cpp index 833996403c..d169035339 100644 --- a/src/plugins/platforms/android/androidjniclipboard.cpp +++ b/src/plugins/platforms/android/androidjniclipboard.cpp @@ -47,7 +47,6 @@ namespace QtAndroidClipboard { QAndroidPlatformClipboard *m_manager = nullptr; - static char const *const QtNativeClassName = "org/qtproject/qt5/android/QtNative"; static JNINativeMethod methods[] = { {"onClipboardDataChanged", "()V", (void *)onClipboardDataChanged} }; diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index dc55ccd615..dabab553c2 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -50,7 +50,7 @@ #include <QGuiApplication> #include <QDebug> -#include <math.h> +#include <QtMath> QT_BEGIN_NAMESPACE @@ -252,7 +252,7 @@ namespace QtAndroidInput QWindowSystemInterface::TouchPoint touchPoint; touchPoint.id = id; touchPoint.pressure = pressure; - touchPoint.rotation = rotation * 180 / M_PI; + touchPoint.rotation = qRadiansToDegrees(rotation); touchPoint.normalPosition = QPointF(double(x / dw), double(y / dh)); touchPoint.state = state; touchPoint.area = QRectF(x - double(minor), diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 594fcbadad..2bdd49dc50 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -540,7 +540,14 @@ static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring para if (sem_init(&m_terminateSemaphore, 0, 0) == -1) return false; - return pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0; + jboolean res = pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0; + + // The service must wait until the QCoreApplication starts otherwise onBind will be + // called too early + if (m_serviceObject) + QtAndroidPrivate::waitForServiceSetup(); + + return res; } static void quitQtCoreApplication(JNIEnv *env, jclass /*clazz*/) @@ -745,6 +752,11 @@ static void onNewIntent(JNIEnv *env, jclass /*cls*/, jobject data) QtAndroidPrivate::handleNewIntent(env, data); } +static jobject onBind(JNIEnv */*env*/, jclass /*cls*/, jobject intent) +{ + return QtAndroidPrivate::callOnBindListener(intent); +} + static JNINativeMethod methods[] = { {"startQtAndroidPlugin", "()Z", (void *)startQtAndroidPlugin}, {"startQtApplication", "(Ljava/lang/String;Ljava/lang/String;)V", (void *)startQtApplication}, @@ -757,7 +769,8 @@ static JNINativeMethod methods[] = { {"updateApplicationState", "(I)V", (void *)updateApplicationState}, {"handleOrientationChanged", "(II)V", (void *)handleOrientationChanged}, {"onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult}, - {"onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent} + {"onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent}, + {"onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind} }; #define FIND_AND_CHECK_CLASS(CLASS_NAME) \ diff --git a/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp b/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp index 08498d0582..ced35c4cfa 100644 --- a/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp +++ b/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp @@ -103,11 +103,7 @@ bool QAndroidPlatformMessageDialogHelper::show(Qt::WindowFlags windowFlags if (!str.isEmpty()) m_javaMessageDialog.callMethod<void>("setDetailedText", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(str).object()); - // http://developer.android.com/design/building-blocks/dialogs.html - // dismissive action on the left, affirmative on the right - // There don't seem to be more fine-grained rules, but the OS X layout - // at least conforms to this one rule and makes the rest deterministic. - const int * currentLayout = buttonLayout(Qt::Horizontal, MacLayout); + const int * currentLayout = buttonLayout(Qt::Horizontal, AndroidLayout); while (*currentLayout != QPlatformDialogHelper::EOL) { int role = (*currentLayout & ~QPlatformDialogHelper::Reverse); addButtons(opt, static_cast<ButtonRole>(role)); diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp index 7185b573cd..763b294660 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.cpp +++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp @@ -69,6 +69,11 @@ #include <QtPlatformHeaders/QEGLNativeContext> +#if QT_CONFIG(vulkan) +#include "qandroidplatformvulkanwindow.h" +#include "qandroidplatformvulkaninstance.h" +#endif + QT_BEGIN_NAMESPACE int QAndroidPlatformIntegration::m_defaultGeometryWidth = 320; @@ -119,6 +124,23 @@ void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteA return 0; } +void *QAndroidPlatformNativeInterface::nativeResourceForWindow(const QByteArray &resource, QWindow *window) +{ +#if QT_CONFIG(vulkan) + if (resource == "vkSurface") { + if (window->surfaceType() == QSurface::VulkanSurface) { + QAndroidPlatformVulkanWindow *w = static_cast<QAndroidPlatformVulkanWindow *>(window->handle()); + // return a pointer to the VkSurfaceKHR, not the value + return w ? w->vkSurface() : nullptr; + } + } +#else + Q_UNUSED(resource); + Q_UNUSED(window); +#endif + return nullptr; +} + void QAndroidPlatformNativeInterface::customEvent(QEvent *event) { if (event->type() != QEvent::User) @@ -243,6 +265,7 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const case ForeignWindows: return QtAndroid::activity(); case ThreadedOpenGL: return !needsBasicRenderloopWorkaround() && QtAndroid::activity(); case RasterGLSurface: return QtAndroid::activity(); + case TopStackedNativeChildWindows: return false; default: return QPlatformIntegration::hasCapability(cap); } @@ -295,6 +318,11 @@ QPlatformWindow *QAndroidPlatformIntegration::createPlatformWindow(QWindow *wind if (!QtAndroid::activity()) return nullptr; +#if QT_CONFIG(vulkan) + if (window->surfaceType() == QSurface::VulkanSurface) + return new QAndroidPlatformVulkanWindow(window); +#endif + return new QAndroidPlatformOpenGLWindow(window, m_eglDisplay); } @@ -443,4 +471,13 @@ void QAndroidPlatformIntegration::setScreenSize(int width, int height) QMetaObject::invokeMethod(m_primaryScreen, "setSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height))); } +#if QT_CONFIG(vulkan) + +QPlatformVulkanInstance *QAndroidPlatformIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const +{ + return new QAndroidPlatformVulkanInstance(instance); +} + +#endif // QT_CONFIG(vulkan) + QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformintegration.h b/src/plugins/platforms/android/qandroidplatformintegration.h index 337f4a9279..c795c499bc 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.h +++ b/src/plugins/platforms/android/qandroidplatformintegration.h @@ -40,6 +40,8 @@ #ifndef QANDROIDPLATFORMINTERATION_H #define QANDROIDPLATFORMINTERATION_H +#include <QtGui/qtguiglobal.h> + #include <qpa/qplatformintegration.h> #include <qpa/qplatformmenu.h> #include <qpa/qplatformnativeinterface.h> @@ -64,6 +66,7 @@ class QAndroidPlatformNativeInterface: public QPlatformNativeInterface { public: void *nativeResourceForIntegration(const QByteArray &resource) override; + void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; std::shared_ptr<AndroidStyle> m_androidStyle; protected: @@ -128,6 +131,10 @@ public: void flushPendingUpdates(); +#if QT_CONFIG(vulkan) + QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; +#endif + private: EGLDisplay m_eglDisplay; QTouchDevice *m_touchDevice; diff --git a/src/plugins/platforms/android/qandroidplatformmenu.cpp b/src/plugins/platforms/android/qandroidplatformmenu.cpp index 06b297a1ad..d9cecebf2c 100644 --- a/src/plugins/platforms/android/qandroidplatformmenu.cpp +++ b/src/plugins/platforms/android/qandroidplatformmenu.cpp @@ -46,7 +46,6 @@ QT_BEGIN_NAMESPACE QAndroidPlatformMenu::QAndroidPlatformMenu() { - m_tag = reinterpret_cast<quintptr>(this); // QMenu will overwrite this later, but we need a unique ID for QtQuick m_enabled = true; m_isVisible = true; } @@ -92,16 +91,6 @@ void QAndroidPlatformMenu::syncSeparatorsCollapsible(bool enable) Q_UNUSED(enable) } -void QAndroidPlatformMenu::setTag(quintptr tag) -{ - m_tag = tag; -} - -quintptr QAndroidPlatformMenu::tag() const -{ - return m_tag; -} - void QAndroidPlatformMenu::setText(const QString &text) { m_text = text; diff --git a/src/plugins/platforms/android/qandroidplatformmenu.h b/src/plugins/platforms/android/qandroidplatformmenu.h index 00968672c5..47e650f2d7 100644 --- a/src/plugins/platforms/android/qandroidplatformmenu.h +++ b/src/plugins/platforms/android/qandroidplatformmenu.h @@ -61,8 +61,6 @@ public: void syncMenuItem(QPlatformMenuItem *menuItem) override; void syncSeparatorsCollapsible(bool enable) override; - void setTag(quintptr tag) override; - quintptr tag() const override; void setText(const QString &text) override; QString text() const; void setIcon(const QIcon &icon) override; @@ -81,7 +79,6 @@ public: private: PlatformMenuItemsType m_menuItems; - quintptr m_tag; QString m_text; QIcon m_icon; bool m_enabled; diff --git a/src/plugins/platforms/android/qandroidplatformmenuitem.cpp b/src/plugins/platforms/android/qandroidplatformmenuitem.cpp index 0591522e55..e24c5f974e 100644 --- a/src/plugins/platforms/android/qandroidplatformmenuitem.cpp +++ b/src/plugins/platforms/android/qandroidplatformmenuitem.cpp @@ -44,7 +44,6 @@ QT_BEGIN_NAMESPACE QAndroidPlatformMenuItem::QAndroidPlatformMenuItem() { - m_tag = reinterpret_cast<quintptr>(this); // QMenu will overwrite this later, but we need a unique ID for QtQuick m_menu = 0; m_isVisible = true; m_isSeparator = false; @@ -54,16 +53,6 @@ QAndroidPlatformMenuItem::QAndroidPlatformMenuItem() m_isEnabled = true; } -void QAndroidPlatformMenuItem::setTag(quintptr tag) -{ - m_tag = tag; -} - -quintptr QAndroidPlatformMenuItem::tag() const -{ - return m_tag; -} - void QAndroidPlatformMenuItem::setText(const QString &text) { m_text = text; diff --git a/src/plugins/platforms/android/qandroidplatformmenuitem.h b/src/plugins/platforms/android/qandroidplatformmenuitem.h index be5240cfa6..b8782f995d 100644 --- a/src/plugins/platforms/android/qandroidplatformmenuitem.h +++ b/src/plugins/platforms/android/qandroidplatformmenuitem.h @@ -49,8 +49,6 @@ class QAndroidPlatformMenuItem: public QPlatformMenuItem { public: QAndroidPlatformMenuItem(); - void setTag(quintptr tag) override; - quintptr tag() const override; void setText(const QString &text) override; QString text() const; @@ -86,7 +84,6 @@ public: void setIconSize(int size) override; private: - quintptr m_tag; QString m_text; QIcon m_icon; QAndroidPlatformMenu *m_menu; diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp index 3b59b293a5..3a79e32abe 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.cpp +++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp @@ -85,7 +85,8 @@ private: # define PROFILE_SCOPE #endif -QAndroidPlatformScreen::QAndroidPlatformScreen():QObject(),QPlatformScreen() +QAndroidPlatformScreen::QAndroidPlatformScreen() + : QObject(), QPlatformScreen() { m_availableGeometry = QRect(0, 0, QAndroidPlatformIntegration::m_defaultGeometryWidth, QAndroidPlatformIntegration::m_defaultGeometryHeight); m_size = QSize(QAndroidPlatformIntegration::m_defaultScreenWidth, QAndroidPlatformIntegration::m_defaultScreenHeight); @@ -100,9 +101,6 @@ QAndroidPlatformScreen::QAndroidPlatformScreen():QObject(),QPlatformScreen() } m_physicalSize.setHeight(QAndroidPlatformIntegration::m_defaultPhysicalSizeHeight); m_physicalSize.setWidth(QAndroidPlatformIntegration::m_defaultPhysicalSizeWidth); - m_redrawTimer.setSingleShot(true); - m_redrawTimer.setInterval(0); - connect(&m_redrawTimer, SIGNAL(timeout()), this, SLOT(doRedraw())); connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidPlatformScreen::applicationStateChanged); } @@ -136,6 +134,16 @@ QWindow *QAndroidPlatformScreen::topLevelAt(const QPoint &p) const return 0; } +bool QAndroidPlatformScreen::event(QEvent *event) +{ + if (event->type() == QEvent::UpdateRequest) { + doRedraw(); + m_updatePending = false; + return true; + } + return QObject::event(event); +} + void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window) { if (window->parent() && window->isRaster()) @@ -209,8 +217,10 @@ void QAndroidPlatformScreen::lower(QAndroidPlatformWindow *window) void QAndroidPlatformScreen::scheduleUpdate() { - if (!m_redrawTimer.isActive()) - m_redrawTimer.start(); + if (!m_updatePending) { + m_updatePending = true; + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); + } } void QAndroidPlatformScreen::setDirty(const QRect &rect) diff --git a/src/plugins/platforms/android/qandroidplatformscreen.h b/src/plugins/platforms/android/qandroidplatformscreen.h index 923c9e8832..f15aeae3fd 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.h +++ b/src/plugins/platforms/android/qandroidplatformscreen.h @@ -89,10 +89,12 @@ public slots: void setSize(const QSize &size); protected: + bool event(QEvent *event) override; + typedef QList<QAndroidPlatformWindow *> WindowStackType; WindowStackType m_windowStack; QRect m_dirtyRect; - QTimer m_redrawTimer; + bool m_updatePending = false; QRect m_availableGeometry; int m_depth; diff --git a/src/plugins/platforms/android/qandroidplatformvulkaninstance.cpp b/src/plugins/platforms/android/qandroidplatformvulkaninstance.cpp new file mode 100644 index 0000000000..a411d0f007 --- /dev/null +++ b/src/plugins/platforms/android/qandroidplatformvulkaninstance.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#include "qandroidplatformvulkaninstance.h" + +QT_BEGIN_NAMESPACE + +QAndroidPlatformVulkanInstance::QAndroidPlatformVulkanInstance(QVulkanInstance *instance) + : m_instance(instance) +{ + m_lib.setFileName(QStringLiteral("vulkan")); + + if (!m_lib.load()) { + qWarning("Failed to load %s", qPrintable(m_lib.fileName())); + return; + } + + init(&m_lib); +} + +void QAndroidPlatformVulkanInstance::createOrAdoptInstance() +{ + initInstance(m_instance, QByteArrayList() << QByteArrayLiteral("VK_KHR_android_surface")); +} + +QAndroidPlatformVulkanInstance::~QAndroidPlatformVulkanInstance() +{ +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformvulkaninstance.h b/src/plugins/platforms/android/qandroidplatformvulkaninstance.h new file mode 100644 index 0000000000..67edcceed9 --- /dev/null +++ b/src/plugins/platforms/android/qandroidplatformvulkaninstance.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#ifndef QANDROIDPLATFORMVULKANINSTANCE_H +#define QANDROIDPLATFORMVULKANINSTANCE_H + +#include <QtVulkanSupport/private/qbasicvulkanplatforminstance_p.h> +#include <QLibrary> + +QT_BEGIN_NAMESPACE + +class QAndroidPlatformVulkanInstance : public QBasicPlatformVulkanInstance +{ +public: + QAndroidPlatformVulkanInstance(QVulkanInstance *instance); + ~QAndroidPlatformVulkanInstance(); + + void createOrAdoptInstance() override; + +private: + QVulkanInstance *m_instance; + QLibrary m_lib; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDPLATFORMVULKANINSTANCE_H diff --git a/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp b/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp new file mode 100644 index 0000000000..cc41a871f3 --- /dev/null +++ b/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#include "qandroidplatformvulkanwindow.h" +#include "qandroidplatformscreen.h" +#include "androidjnimain.h" +#include "qandroideventdispatcher.h" +#include "androiddeadlockprotector.h" + +#include <QSurfaceFormat> +#include <qpa/qwindowsysteminterface.h> +#include <qpa/qplatformscreen.h> + +#include <android/native_window.h> +#include <android/native_window_jni.h> + +QT_BEGIN_NAMESPACE + +QAndroidPlatformVulkanWindow::QAndroidPlatformVulkanWindow(QWindow *window) + : QAndroidPlatformWindow(window), + m_nativeSurfaceId(-1), + m_nativeWindow(nullptr), + m_vkSurface(0), + m_createVkSurface(nullptr), + m_destroyVkSurface(nullptr) +{ +} + +QAndroidPlatformVulkanWindow::~QAndroidPlatformVulkanWindow() +{ + m_surfaceWaitCondition.wakeOne(); + lockSurface(); + if (m_nativeSurfaceId != -1) + QtAndroid::destroySurface(m_nativeSurfaceId); + clearSurface(); + unlockSurface(); +} + +void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect) +{ + if (rect == geometry()) + return; + + m_oldGeometry = geometry(); + + QAndroidPlatformWindow::setGeometry(rect); + if (m_nativeSurfaceId != -1) + QtAndroid::setSurfaceGeometry(m_nativeSurfaceId, rect); + + QRect availableGeometry = screen()->availableGeometry(); + if (m_oldGeometry.width() == 0 + && m_oldGeometry.height() == 0 + && rect.width() > 0 + && rect.height() > 0 + && availableGeometry.width() > 0 + && availableGeometry.height() > 0) { + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size())); + } + + if (rect.topLeft() != m_oldGeometry.topLeft()) + repaint(QRegion(rect)); +} + +void QAndroidPlatformVulkanWindow::applicationStateChanged(Qt::ApplicationState state) +{ + QAndroidPlatformWindow::applicationStateChanged(state); + if (state <= Qt::ApplicationHidden) { + lockSurface(); + if (m_nativeSurfaceId != -1) { + QtAndroid::destroySurface(m_nativeSurfaceId); + m_nativeSurfaceId = -1; + } + clearSurface(); + unlockSurface(); + } +} + +QSurfaceFormat QAndroidPlatformVulkanWindow::format() const +{ + return window()->requestedFormat(); +} + +void QAndroidPlatformVulkanWindow::clearSurface() +{ + if (m_vkSurface && m_destroyVkSurface) { + m_destroyVkSurface(window()->vulkanInstance()->vkInstance(), m_vkSurface, nullptr); + m_vkSurface = 0; + } + + if (m_nativeWindow) { + ANativeWindow_release(m_nativeWindow); + m_nativeWindow = nullptr; + } +} + +void QAndroidPlatformVulkanWindow::sendExpose() +{ + QRect availableGeometry = screen()->availableGeometry(); + if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0) + QWindowSystemInterface::handleExposeEvent(window(), QRegion(QRect(QPoint(), geometry().size()))); +} + +void QAndroidPlatformVulkanWindow::surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) +{ + Q_UNUSED(jniEnv); + Q_UNUSED(w); + Q_UNUSED(h); + + lockSurface(); + m_androidSurfaceObject = surface; + if (surface) + m_surfaceWaitCondition.wakeOne(); + unlockSurface(); + + if (surface) + sendExpose(); +} + +VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface() +{ + if (QAndroidEventDispatcherStopper::stopped()) + return &m_vkSurface; + + bool needsExpose = false; + if (!m_vkSurface) { + clearSurface(); + + QMutexLocker lock(&m_surfaceMutex); + if (m_nativeSurfaceId == -1) { + AndroidDeadlockProtector protector; + if (!protector.acquire()) + return &m_vkSurface; + const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint); + m_nativeSurfaceId = QtAndroid::createSurface(this, geometry(), windowStaysOnTop, 32); + m_surfaceWaitCondition.wait(&m_surfaceMutex); + } + + if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid()) + return &m_vkSurface; + + QJNIEnvironmentPrivate env; + m_nativeWindow = ANativeWindow_fromSurface(env, m_androidSurfaceObject.object()); + + VkAndroidSurfaceCreateInfoKHR surfaceInfo; + memset(&surfaceInfo, 0, sizeof(surfaceInfo)); + surfaceInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + surfaceInfo.window = m_nativeWindow; + QVulkanInstance *inst = window()->vulkanInstance(); + if (!inst) { + qWarning("Attempted to create Vulkan surface without an instance; was QWindow::setVulkanInstance() called?"); + return &m_vkSurface; + } + if (!m_createVkSurface) { + m_createVkSurface = reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>( + inst->getInstanceProcAddr("vkCreateAndroidSurfaceKHR")); + } + if (!m_destroyVkSurface) { + m_destroyVkSurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>( + inst->getInstanceProcAddr("vkDestroySurfaceKHR")); + } + VkResult err = m_createVkSurface(inst->vkInstance(), &surfaceInfo, nullptr, &m_vkSurface); + if (err != VK_SUCCESS) + qWarning("Failed to create Android VkSurface: %d", err); + + needsExpose = true; + } + + if (needsExpose) + sendExpose(); + + return &m_vkSurface; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformvulkanwindow.h b/src/plugins/platforms/android/qandroidplatformvulkanwindow.h new file mode 100644 index 0000000000..19eca98720 --- /dev/null +++ b/src/plugins/platforms/android/qandroidplatformvulkanwindow.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#ifndef QANDROIDPLATFORMVULKANWINDOW_H +#define QANDROIDPLATFORMVULKANWINDOW_H + +#if defined(VULKAN_H_) && !defined(VK_USE_PLATFORM_ANDROID_KHR) +#error "vulkan.h included without Android WSI" +#endif + +#define VK_USE_PLATFORM_ANDROID_KHR + +#include <QWaitCondition> +#include <QtCore/private/qjni_p.h> + +#include "androidsurfaceclient.h" +#include "qandroidplatformwindow.h" + +#include "qandroidplatformvulkaninstance.h" + +QT_BEGIN_NAMESPACE + +class QAndroidPlatformVulkanWindow : public QAndroidPlatformWindow, public AndroidSurfaceClient +{ +public: + explicit QAndroidPlatformVulkanWindow(QWindow *window); + ~QAndroidPlatformVulkanWindow(); + + void setGeometry(const QRect &rect) override; + QSurfaceFormat format() const override; + void applicationStateChanged(Qt::ApplicationState) override; + + VkSurfaceKHR *vkSurface(); + +protected: + void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) override; + +private: + void sendExpose(); + void clearSurface(); + + int m_nativeSurfaceId; + ANativeWindow *m_nativeWindow; + QJNIObjectPrivate m_androidSurfaceObject; + QWaitCondition m_surfaceWaitCondition; + QSurfaceFormat m_format; + QRect m_oldGeometry; + VkSurfaceKHR m_vkSurface; + PFN_vkCreateAndroidSurfaceKHR m_createVkSurface; + PFN_vkDestroySurfaceKHR m_destroyVkSurface; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDPLATFORMVULKANWINDOW_H diff --git a/src/plugins/platforms/android/qandroidplatformwindow.cpp b/src/plugins/platforms/android/qandroidplatformwindow.cpp index 424cfefc6c..c095f51fa3 100644 --- a/src/plugins/platforms/android/qandroidplatformwindow.cpp +++ b/src/plugins/platforms/android/qandroidplatformwindow.cpp @@ -56,7 +56,7 @@ QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window) m_windowState = Qt::WindowNoState; static QAtomicInt winIdGenerator(1); m_windowId = winIdGenerator.fetchAndAddRelaxed(1); - setWindowState(window->windowState()); + setWindowState(window->windowStates()); } void QAndroidPlatformWindow::lower() @@ -73,7 +73,6 @@ void QAndroidPlatformWindow::raise() void QAndroidPlatformWindow::setGeometry(const QRect &rect) { QWindowSystemInterface::handleGeometryChange(window(), rect); - QPlatformWindow::setGeometry(rect); } void QAndroidPlatformWindow::setVisible(bool visible) @@ -98,7 +97,7 @@ void QAndroidPlatformWindow::setVisible(bool visible) QPlatformWindow::setVisible(visible); } -void QAndroidPlatformWindow::setWindowState(Qt::WindowState state) +void QAndroidPlatformWindow::setWindowState(Qt::WindowStates state) { if (m_windowState == state) return; diff --git a/src/plugins/platforms/android/qandroidplatformwindow.h b/src/plugins/platforms/android/qandroidplatformwindow.h index 91cb1e76e6..f2e51bd3df 100644 --- a/src/plugins/platforms/android/qandroidplatformwindow.h +++ b/src/plugins/platforms/android/qandroidplatformwindow.h @@ -59,7 +59,7 @@ public: void setVisible(bool visible) override; - void setWindowState(Qt::WindowState state) override; + void setWindowState(Qt::WindowStates state) override; void setWindowFlags(Qt::WindowFlags flags) override; Qt::WindowFlags windowFlags() const; void setParent(const QPlatformWindow *window) override; @@ -91,7 +91,7 @@ protected: protected: Qt::WindowFlags m_windowFlags; - Qt::WindowState m_windowState; + Qt::WindowStates m_windowState; WId m_windowId; diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 5a00e1b7ec..5354bdafbc 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -2,10 +2,12 @@ TARGET = qcocoa SOURCES += main.mm \ qcocoaintegration.mm \ + qcocoascreen.mm \ qcocoatheme.mm \ qcocoabackingstore.mm \ qcocoawindow.mm \ qnsview.mm \ + qnswindow.mm \ qnsviewaccessibility.mm \ qnswindowdelegate.mm \ qcocoanativeinterface.mm \ @@ -34,10 +36,12 @@ SOURCES += main.mm \ messages.cpp HEADERS += qcocoaintegration.h \ + qcocoascreen.h \ qcocoatheme.h \ qcocoabackingstore.h \ qcocoawindow.h \ qnsview.h \ + qnswindow.h \ qnswindowdelegate.h \ qcocoanativeinterface.h \ qcocoaeventdispatcher.h \ @@ -72,12 +76,12 @@ qtConfig(opengl.*) { RESOURCES += qcocoaresources.qrc -LIBS += -framework AppKit -framework Carbon -framework IOKit -lcups +LIBS += -framework AppKit -framework Carbon -framework IOKit -framework QuartzCore -lcups QT += \ core-private gui-private \ accessibility_support-private clipboard_support-private theme_support-private \ - fontdatabase_support-private graphics_support-private cgl_support-private + fontdatabase_support-private graphics_support-private CONFIG += no_app_extension_api_only diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index b15c486e9d..8b6dcb35a6 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -41,8 +41,6 @@ #include <QtGui/qaccessible.h> #include <private/qcore_mac_p.h> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE #ifndef QT_NO_ACCESSIBILITY @@ -369,7 +367,7 @@ id getValueAttribute(QAccessibleInterface *interface) QString text; if (interface->state().passwordEdit) { // return round password replacement chars - text = QString(end, QChar(kBulletUnicode)); + text = QString(end, QChar(0x2022)); } else { // VoiceOver will read out the entire text string at once when returning // text as a value. For large text edits the size of the returned string diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 35ac7182af..5392804d62 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -284,7 +284,7 @@ QT_END_NAMESPACE inLaunch = false; if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { - if (QSysInfo::macVersion() >= QSysInfo::MV_10_12) { + if (__builtin_available(macOS 10.12, *)) { // Move the application window to front to avoid launching behind the terminal. // Ignoring other apps is necessary (we must ignore the terminal), but makes // Qt apps play slightly less nice with other apps when lanching from Finder diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 5ed455fd71..a0bc204013 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -42,6 +42,8 @@ #include <QtGraphicsSupport/private/qrasterbackingstore_p.h> +#include <private/qcore_mac_p.h> + QT_BEGIN_NAMESPACE class QCocoaBackingStore : public QRasterBackingStore @@ -53,7 +55,9 @@ public: void flush(QWindow *, const QRegion &, const QPoint &) Q_DECL_OVERRIDE; private: + bool windowHasUnifiedToolbar() const; QImage::Format format() const Q_DECL_OVERRIDE; + void redrawRoundedBottomCorners(CGRect) const; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 1d7ad772dc..57a03905ab 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -44,6 +44,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcCocoaBackingStore, "qt.qpa.cocoa.backingstore"); + QCocoaBackingStore::QCocoaBackingStore(QWindow *window) : QRasterBackingStore(window) { @@ -51,26 +53,239 @@ QCocoaBackingStore::QCocoaBackingStore(QWindow *window) QCocoaBackingStore::~QCocoaBackingStore() { - if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle())) - [qnsview_cast(cocoaWindow->view()) clearBackingStore:this]; +} + +bool QCocoaBackingStore::windowHasUnifiedToolbar() const +{ + Q_ASSERT(window()->handle()); + return static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient; } QImage::Format QCocoaBackingStore::format() const { - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle()); - if (cocoaWindow && cocoaWindow->m_drawContentBorderGradient) + if (windowHasUnifiedToolbar()) return QImage::Format_ARGB32_Premultiplied; return QRasterBackingStore::format(); } +#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) +static const NSCompositingOperation NSCompositingOperationCopy = NSCompositeCopy; +static const NSCompositingOperation NSCompositingOperationSourceOver = NSCompositeSourceOver; +#endif + +/*! + Flushes the given \a region from the specified \a window onto the + screen. + + The \a window is the top level window represented by this backingstore, + or a non-transient child of that window. + + If the \a window is a child window, the \a region will be in child window + coordinates, and the \a offset will be the child window's offset in relation + to the backingstore's top level window. +*/ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { if (m_image.isNull()) return; - if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle())) - [qnsview_cast(cocoaWindow->view()) flushBackingStore:this region:region offset:offset]; + // Use local pool so that any stale image references are cleaned up after flushing + QMacAutoReleasePool pool; + + const QWindow *topLevelWindow = this->window(); + + Q_ASSERT(topLevelWindow->handle() && window->handle()); + Q_ASSERT(!topLevelWindow->handle()->isForeignWindow() && !window->handle()->isForeignWindow()); + + QNSView *topLevelView = qnsview_cast(static_cast<QCocoaWindow *>(topLevelWindow->handle())->view()); + QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(window->handle())->view()); + + if (lcCocoaBackingStore().isDebugEnabled()) { + QString targetViewDescription; + if (view != topLevelView) { + QDebug targetDebug(&targetViewDescription); + targetDebug << "onto" << topLevelView << "at" << offset; + } + qCDebug(lcCocoaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription); + } + + // Prevent potentially costly color conversion by assigning the display color space + // to the backingstore image. This does not copy the underlying image data. + CGColorSpaceRef displayColorSpace = view.window.screen.colorSpace.CGColorSpace; + QCFType<CGImageRef> cgImage = CGImageCreateCopyWithColorSpace( + QCFType<CGImageRef>(m_image.toCGImage()), displayColorSpace); + + if (view.layer) { + // In layer-backed mode, locking focus on a view does not give the right + // view transformation, and doesn't give us a graphics context to render + // via when drawing outside of the display cycle. Instead we tell AppKit + // that we want to update the layer's content, via [NSView wantsUpdateLayer], + // which result in AppKit not creating a backingstore for each layer, and + // we then directly set the layer's backingstore (content) to our backingstore, + // masked to the part of the subview that is relevant. + // FIXME: Figure out if there's a way to do partial updates + view.layer.contents = (__bridge id)static_cast<CGImageRef>(cgImage); + if (view != topLevelView) { + view.layer.contentsRect = CGRectApplyAffineTransform( + [view convertRect:view.bounds toView:topLevelView], + // The contentsRect is in unit coordinate system + CGAffineTransformMakeScale(1.0 / m_image.width(), 1.0 / m_image.height())); + } + return; + } + + // Normally a NSView is drawn via drawRect, as part of the display cycle in the + // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each + // individual view, starting with the top level and then traversing any subviews, + // calling drawRect for each of them. This pull model results in expose events + // sent to Qt, which result in drawing to the backingstore and flushing it. + // Qt may also decide to paint and flush the backingstore via e.g. timers, + // or other events such as mouse events, in which case we're in a push model. + // If there is no focused view, it means we're in the latter case, and need + // to manually flush the NSWindow after drawing to its graphic context. + const bool drawingOutsideOfDisplayCycle = ![NSView focusView]; + + // We also need to ensure the flushed view has focus, so that the graphics + // context is set up correctly (coordinate system, clipping, etc). Outside + // of the normal display cycle there is no focused view, as explained above, + // so we have to handle it manually. There's also a corner case inside the + // normal display cycle due to way QWidgetBackingStore composits native child + // widgets, where we'll get a flush of a native child during the drawRect of + // its parent/ancestor, and the parent/ancestor being the one locked by AppKit. + // In this case we also need to lock and unlock focus manually. + const bool shouldHandleViewLockManually = [NSView focusView] != view; + if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) { + qWarning() << "failed to lock focus of" << view; + return; + } + + const qreal devicePixelRatio = m_image.devicePixelRatio(); + + // If the flushed window is a content view, and not in unified toolbar mode, + // we can get away with copying the backingstore instead of blending. + const NSCompositingOperation compositingOperation = static_cast<QCocoaWindow *>( + window->handle())->isContentView() && !windowHasUnifiedToolbar() ? + NSCompositingOperationCopy : NSCompositingOperationSourceOver; + +#ifdef QT_DEBUG + static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults] + boolForKey:@"QtCocoaDebugBackingStoreFlush"]; +#endif + + // ------------------------------------------------------------------------- + + // The current contexts is typically a NSWindowGraphicsContext, but can be + // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode. + // If we need to distinguish things here in the future, we can use e.g. + // [NSGraphicsContext drawingToScreen], or the attributes of the context. + NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; + Q_ASSERT_X(graphicsContext, "QCocoaBackingStore", + "Focusing the view should give us a current graphics context"); + + // Create temporary image to use for blitting, without copying image data + NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease]; + + QRegion clippedRegion = region; + for (QWindow *w = window; w; w = w->parent()) { + if (!w->mask().isEmpty()) { + clippedRegion &= w == window ? w->mask() + : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)))); + } + } + + for (const QRect &viewLocalRect : clippedRegion) { + QPoint backingStoreOffset = viewLocalRect.topLeft() + offset; + QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio); + if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context + backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height())); + + CGRect viewRect = viewLocalRect.toCGRect(); + + if (windowHasUnifiedToolbar()) + NSDrawWindowBackground(viewRect); + + [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect() + operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil]; + +#ifdef QT_DEBUG + if (Q_UNLIKELY(debugBackingStoreFlush)) { + [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set]; + [NSBezierPath fillRect:viewRect]; + + if (drawingOutsideOfDisplayCycle) { + [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set]; + [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint() + toPoint:viewLocalRect.bottomRight().toCGPoint()]; + } + } +#endif + } + + QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle()); + if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) { + [topLevelView.window invalidateShadow]; + topLevelCocoaWindow->m_needsInvalidateShadow = false; + } + + // ------------------------------------------------------------------------- + + if (shouldHandleViewLockManually) + [view unlockFocus]; + + if (drawingOutsideOfDisplayCycle) { + redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]); + [view.window flushWindow]; + } +} + +/* + When drawing outside of the display cycle, which Qt Widget does a lot, + we end up drawing over the NSThemeFrame, losing the rounded corners of + windows in the process. + + To work around this, until we've enabled updates via setNeedsDisplay and/or + enabled layer-backed views, we ask the NSWindow to redraw the bottom corners + if they intersect with the flushed region. + + This is the same logic used internally by e.g [NSView displayIfNeeded], + [NSRulerView _scrollToMatchContentView], and [NSClipView _immediateScrollToPoint:], + as well as the workaround used by WebKit to fix a similar bug: + + https://trac.webkit.org/changeset/85376/webkit +*/ +void QCocoaBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const +{ +#if !defined(QT_APPLE_NO_PRIVATE_APIS) + Q_ASSERT(this->window()->handle()); + NSWindow *window = static_cast<QCocoaWindow *>(this->window()->handle())->nativeWindow(); + + static SEL intersectBottomCornersWithRect = NSSelectorFromString( + [NSString stringWithFormat:@"_%s%s:", "intersectBottomCorners", "WithRect"]); + if (NSMethodSignature *signature = [window methodSignatureForSelector:intersectBottomCornersWithRect]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.target = window; + invocation.selector = intersectBottomCornersWithRect; + [invocation setArgument:&windowRect atIndex:2]; + [invocation invoke]; + + NSRect cornerOverlap = NSZeroRect; + [invocation getReturnValue:&cornerOverlap]; + if (!NSIsEmptyRect(cornerOverlap)) { + static SEL maskRoundedBottomCorners = NSSelectorFromString( + [NSString stringWithFormat:@"_%s%s:", "maskRounded", "BottomCorners"]); + if ((signature = [window methodSignatureForSelector:maskRoundedBottomCorners])) { + invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.target = window; + invocation.selector = maskRoundedBottomCorners; + [invocation setArgument:&cornerOverlap atIndex:2]; + [invocation invoke]; + } + } + } +#else + Q_UNUSED(windowRect); +#endif } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h index 9ebb090989..c7277a47bf 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.h +++ b/src/plugins/platforms/cocoa/qcocoadrag.h @@ -55,7 +55,7 @@ public: QCocoaDrag(); ~QCocoaDrag(); - QMimeData *platformDropData() Q_DECL_OVERRIDE; + QMimeData *dragMimeData(); Qt::DropAction drag(QDrag *m_drag) Q_DECL_OVERRIDE; Qt::DropAction defaultAction(Qt::DropActions possibleActions, diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index c71e80d191..3bd0b05725 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -68,7 +68,7 @@ void QCocoaDrag::setLastMouseEvent(NSEvent *event, NSView *view) m_lastView = view; } -QMimeData *QCocoaDrag::platformDropData() +QMimeData *QCocoaDrag::dragMimeData() { if (m_drag) return m_drag->mimeData(); diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h index 70887c41c9..2ffc1395ba 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -177,8 +177,6 @@ public: void maybeCancelWaitForMoreEvents(); void ensureNSAppInitialized(); - void removeQueuedUserInputEvents(int nsWinNumber); - QCFSocketNotifier cfSocketNotifier; QList<void *> queuedUserInputEvents; // NSEvent * CFRunLoopSourceRef postedEventsSource; diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index b22f1b1f54..bf9ba4eccf 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -86,7 +86,6 @@ #include <qdebug.h> #include <AppKit/AppKit.h> -#include <Carbon/Carbon.h> QT_BEGIN_NAMESPACE @@ -285,7 +284,7 @@ bool QCocoaEventDispatcher::hasPendingEvents() { extern uint qGlobalPostedEventsCount(); extern bool qt_is_gui_used; //qapplication.cpp - return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue())); + return qGlobalPostedEventsCount() || (qt_is_gui_used && !CFRunLoopIsWaiting(CFRunLoopGetMain())); } static bool IsMouseOrKeyEvent( NSEvent* event ) @@ -900,21 +899,6 @@ void QCocoaEventDispatcherPrivate::processPostedEvents() } } -void QCocoaEventDispatcherPrivate::removeQueuedUserInputEvents(int nsWinNumber) -{ - if (nsWinNumber) { - int eventIndex = queuedUserInputEvents.size(); - - while (--eventIndex >= 0) { - NSEvent * nsevent = static_cast<NSEvent *>(queuedUserInputEvents.at(eventIndex)); - if ([nsevent windowNumber] == nsWinNumber) { - queuedUserInputEvents.removeAt(eventIndex); - [nsevent release]; - } - } - } -} - void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info) diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 74148b7cbf..fa123550ef 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -163,7 +163,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); [mSavePanel setDelegate:self]; #if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan) + if (__builtin_available(macOS 10.11, *)) mOpenPanel.accessoryViewDisclosed = YES; #endif @@ -231,7 +231,7 @@ static QString strippedText(QString s) [mOpenPanel beginWithCompletionHandler:^(NSInteger result){ mReturnCode = result; if (mHelper) - mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSOKButton); + mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSModalResponseOK); }]; } } @@ -260,12 +260,12 @@ static QString strippedText(QString s) QCocoaMenuBar::resetKnownMenuItemsToQt(); QAbstractEventDispatcher::instance()->interrupt(); - return (mReturnCode == NSOKButton); + return (mReturnCode == NSModalResponseOK); } - (QPlatformDialogHelper::DialogCode)dialogResultCode { - return (mReturnCode == NSOKButton) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; + return (mReturnCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; } - (void)showWindowModalSheet:(QWindow *)parent @@ -286,7 +286,7 @@ static QString strippedText(QString s) [mSavePanel beginSheetModalForWindow:nsparent completionHandler:^(NSInteger result){ mReturnCode = result; if (mHelper) - mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSOKButton); + mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSModalResponseOK); }]; } diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index 1a66cec0e3..732b50d29e 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -84,6 +84,7 @@ private: NSOpenGLContext *m_shareContext; QSurfaceFormat m_format; QPointer<QWindow> m_currentWindow; + bool m_didCheckForSoftwareContext; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 9e688f4d1b..b656025ec7 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -42,7 +42,6 @@ #include "qcocoahelpers.h" #include <qdebug.h> #include <QtCore/private/qcore_mac_p.h> -#include <QtCglSupport/private/cglconvenience_p.h> #include <QtPlatformHeaders/qcocoanativecontext.h> #include <dlfcn.h> @@ -122,7 +121,8 @@ QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLCo const QVariant &nativeHandle) : m_context(nil), m_shareContext(nil), - m_format(format) + m_format(format), + m_didCheckForSoftwareContext(false) { if (!nativeHandle.isNull()) { if (!nativeHandle.canConvert<QCocoaNativeContext>()) { @@ -153,9 +153,32 @@ QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLCo QMacAutoReleasePool pool; // For the SG Canvas render thread - // create native context for the requested pixel format and share - NSOpenGLPixelFormat *pixelFormat = static_cast <NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat(m_format)); m_shareContext = share ? static_cast<QCocoaGLContext *>(share)->nsOpenGLContext() : nil; + + if (m_shareContext) { + // Allow sharing between 3.2 Core and 4.1 Core profile versions in + // cases where NSOpenGLContext creates a 4.1 context where a 3.2 + // context was requested. Due to the semantics of QSurfaceFormat + // this 4.1 version can find its way onto the format for the new + // context, even though it was at no point requested by the user. + GLint shareContextRequestedProfile; + [m_shareContext.pixelFormat getValues:&shareContextRequestedProfile + forAttribute:NSOpenGLPFAOpenGLProfile forVirtualScreen:0]; + auto shareContextActualProfile = share->format().version(); + + if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core && + shareContextActualProfile >= qMakePair(4, 1)) { + + // There is a mismatch, downgrade requested format to make the + // NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext will + // fail to create a new context if there is a mismatch). + if (m_format.version() >= qMakePair(4, 1)) + m_format.setVersion(3, 2); + } + } + + // create native context for the requested pixel format and share + NSOpenGLPixelFormat *pixelFormat = createNSOpenGLPixelFormat(m_format); m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext]; // retry without sharing on context creation failure. @@ -202,7 +225,6 @@ QVariant QCocoaGLContext::nativeHandle() const return QVariant::fromValue<QCocoaNativeContext>(QCocoaNativeContext(m_context)); } -// Match up with createNSOpenGLPixelFormat! QSurfaceFormat QCocoaGLContext::format() const { return m_format; @@ -220,6 +242,9 @@ void QCocoaGLContext::windowWasHidden() void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) { + if (surface->surface()->surfaceClass() == QSurface::Offscreen) + return; // Nothing to do + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); setActiveWindow(window); @@ -231,11 +256,29 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) Q_ASSERT(surface->surface()->supportsOpenGL()); QMacAutoReleasePool pool; + [m_context makeCurrentContext]; + + if (surface->surface()->surfaceClass() == QSurface::Offscreen) + return true; QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); setActiveWindow(window); - [m_context makeCurrentContext]; + // Disable high-resolution surfaces when using the software renderer, which has the + // problem that the system silently falls back to a to using a low-resolution buffer + // when a high-resolution buffer is requested. This is not detectable using the NSWindow + // convertSizeToBacking and backingScaleFactor APIs. A typical result of this is that Qt + // will display a quarter of the window content when running in a virtual machine. + if (!m_didCheckForSoftwareContext) { + m_didCheckForSoftwareContext = true; + + const GLubyte* renderer = glGetString(GL_RENDERER); + if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { + NSView *view = static_cast<QCocoaWindow *>(surface)->m_view; + [view setWantsBestResolutionOpenGLSurface:NO]; + } + } + update(); return true; } @@ -362,7 +405,64 @@ void QCocoaGLContext::update() NSOpenGLPixelFormat *QCocoaGLContext::createNSOpenGLPixelFormat(const QSurfaceFormat &format) { - return static_cast<NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat(format)); + QVector<NSOpenGLPixelFormatAttribute> attrs; + + if (format.swapBehavior() == QSurfaceFormat::DoubleBuffer + || format.swapBehavior() == QSurfaceFormat::DefaultSwapBehavior) + attrs.append(NSOpenGLPFADoubleBuffer); + else if (format.swapBehavior() == QSurfaceFormat::TripleBuffer) + attrs.append(NSOpenGLPFATripleBuffer); + + + // Select OpenGL profile + attrs << NSOpenGLPFAOpenGLProfile; + if (format.profile() == QSurfaceFormat::CoreProfile) { + if (format.version() >= qMakePair(4, 1)) + attrs << NSOpenGLProfileVersion4_1Core; + else if (format.version() >= qMakePair(3, 2)) + attrs << NSOpenGLProfileVersion3_2Core; + else + attrs << NSOpenGLProfileVersionLegacy; + } else { + attrs << NSOpenGLProfileVersionLegacy; + } + + if (format.depthBufferSize() > 0) + attrs << NSOpenGLPFADepthSize << format.depthBufferSize(); + if (format.stencilBufferSize() > 0) + attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize(); + if (format.alphaBufferSize() > 0) + attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize(); + if ((format.redBufferSize() > 0) && + (format.greenBufferSize() > 0) && + (format.blueBufferSize() > 0)) { + const int colorSize = format.redBufferSize() + + format.greenBufferSize() + + format.blueBufferSize(); + attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy; + } + + if (format.samples() > 0) { + attrs << NSOpenGLPFAMultisample + << NSOpenGLPFASampleBuffers << (NSOpenGLPixelFormatAttribute) 1 + << NSOpenGLPFASamples << (NSOpenGLPixelFormatAttribute) format.samples(); + } + + if (format.stereo()) + attrs << NSOpenGLPFAStereo; + + attrs << NSOpenGLPFAAllowOfflineRenderers; + + QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); + if (!useLayer.isEmpty() && useLayer.toInt() > 0) { + // Disable the software rendering fallback. This makes compositing + // OpenGL and raster NSViews using Core Animation layers possible. + attrs << NSOpenGLPFANoRecovery; + } + + attrs << 0; + + return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; } NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 4478895538..7810733255 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -55,6 +55,9 @@ #include <QtGui/qpalette.h> #include <QtGui/qscreen.h> +#include <objc/runtime.h> +#include <objc/message.h> + Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView)); QT_BEGIN_NAMESPACE @@ -188,5 +191,131 @@ QT_END_NAMESPACE QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper); +// ------------------------------------------------------------------------- + +// Depending on the ABI of the platform, we may need to use objc_msgSendSuper_stret: +// - http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html +// - https://lists.apple.com/archives/cocoa-dev/2008/Feb/msg02338.html +template <typename T> +struct objc_msgsend_requires_stret +{ static const bool value = +#if defined(Q_PROCESSOR_X86) + // Any return value larger than two registers on i386/x86_64 + sizeof(T) > sizeof(void*) * 2; +#elif defined(Q_PROCESSOR_ARM_32) + // Any return value larger than a single register on arm + sizeof(T) > sizeof(void*); +#elif defined(Q_PROCESSOR_ARM_64) + // Stret not used on arm64 + false; +#endif +}; + +template <> +struct objc_msgsend_requires_stret<void> +{ static const bool value = false; }; + +template <typename ReturnType, typename... Args> +ReturnType qt_msgSendSuper(id receiver, SEL selector, Args... args) +{ + static_assert(!objc_msgsend_requires_stret<ReturnType>::value, + "The given return type requires stret on this platform"); + + typedef ReturnType (*SuperFn)(objc_super *, SEL, Args...); + SuperFn superFn = reinterpret_cast<SuperFn>(objc_msgSendSuper); + objc_super sup = { receiver, [receiver superclass] }; + return superFn(&sup, selector, args...); +} + +template <typename ReturnType, typename... Args> +ReturnType qt_msgSendSuper_stret(id receiver, SEL selector, Args... args) +{ + static_assert(objc_msgsend_requires_stret<ReturnType>::value, + "The given return type does not use stret on this platform"); + + typedef void (*SuperStretFn)(ReturnType *, objc_super *, SEL, Args...); + SuperStretFn superStretFn = reinterpret_cast<SuperStretFn>(objc_msgSendSuper_stret); + + objc_super sup = { receiver, [receiver superclass] }; + ReturnType ret; + superStretFn(&ret, &sup, selector, args...); + return ret; +} + +template<typename... Args> +class QSendSuperHelper { +public: + QSendSuperHelper(id receiver, SEL sel, Args... args) + : m_receiver(receiver), m_selector(sel), m_args(std::make_tuple(args...)), m_sent(false) + { + } + + ~QSendSuperHelper() + { + if (!m_sent) + msgSendSuper<void>(m_args); + } + + template <typename ReturnType> + operator ReturnType() + { +#if defined(QT_DEBUG) + Method method = class_getInstanceMethod(object_getClass(m_receiver), m_selector); + char returnTypeEncoding[256]; + method_getReturnType(method, returnTypeEncoding, sizeof(returnTypeEncoding)); + NSUInteger alignedReturnTypeSize = 0; + NSGetSizeAndAlignment(returnTypeEncoding, nullptr, &alignedReturnTypeSize); + Q_ASSERT(alignedReturnTypeSize == sizeof(ReturnType)); +#endif + m_sent = true; + return msgSendSuper<ReturnType>(m_args); + } + +private: + template <std::size_t... Ts> + struct index {}; + + template <std::size_t N, std::size_t... Ts> + struct gen_seq : gen_seq<N - 1, N - 1, Ts...> {}; + + template <std::size_t... Ts> + struct gen_seq<0, Ts...> : index<Ts...> {}; + + template <typename ReturnType, bool V> + using if_requires_stret = typename std::enable_if<objc_msgsend_requires_stret<ReturnType>::value == V, ReturnType>::type; + + template <typename ReturnType, std::size_t... Is> + if_requires_stret<ReturnType, false> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + { + return qt_msgSendSuper<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); + } + + template <typename ReturnType, std::size_t... Is> + if_requires_stret<ReturnType, true> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + { + return qt_msgSendSuper_stret<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); + } + + template <typename ReturnType> + ReturnType msgSendSuper(std::tuple<Args...>& args) + { + return msgSendSuper<ReturnType>(args, gen_seq<sizeof...(Args)>{}); + } + + id m_receiver; + SEL m_selector; + std::tuple<Args...> m_args; + bool m_sent; +}; + +template<typename... Args> +QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, Args... args) +{ + return QSendSuperHelper<Args...>(receiver, selector, args...); +} + +// Same as calling super, but the super_class field resolved at runtime instead of compile time +#define qt_objcDynamicSuper(...) qt_objcDynamicSuperHelper(self, _cmd, ##__VA_ARGS__) + #endif //QCOCOAHELPERS_H diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 5d0f13c5a9..9f9618177d 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -55,8 +55,6 @@ #include <algorithm> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaCocoaWindow, "qt.qpa.cocoa.window"); @@ -407,6 +405,7 @@ QT_END_NAMESPACE self.panelContents.needsDisplay = YES; self.needsDisplay = YES; + [super layout]; } @end diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index ecdd20c4dc..2fc5156d24 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -58,60 +58,11 @@ QT_BEGIN_NAMESPACE -class QCocoaScreen : public QPlatformScreen -{ -public: - QCocoaScreen(int screenIndex); - ~QCocoaScreen(); - - // ---------------------------------------------------- - // Virtual methods overridden from QPlatformScreen - QPixmap grabWindow(WId window, int x, int y, int width, int height) const Q_DECL_OVERRIDE; - QRect geometry() const Q_DECL_OVERRIDE { return m_geometry; } - QRect availableGeometry() const Q_DECL_OVERRIDE { return m_availableGeometry; } - int depth() const Q_DECL_OVERRIDE { return m_depth; } - QImage::Format format() const Q_DECL_OVERRIDE { return m_format; } - qreal devicePixelRatio() const Q_DECL_OVERRIDE; - QSizeF physicalSize() const Q_DECL_OVERRIDE { return m_physicalSize; } - QDpi logicalDpi() const Q_DECL_OVERRIDE { return m_logicalDpi; } - qreal refreshRate() const Q_DECL_OVERRIDE { return m_refreshRate; } - QString name() const Q_DECL_OVERRIDE { return m_name; } - QPlatformCursor *cursor() const Q_DECL_OVERRIDE { return m_cursor; } - QWindow *topLevelAt(const QPoint &point) const Q_DECL_OVERRIDE; - QList<QPlatformScreen *> virtualSiblings() const Q_DECL_OVERRIDE { return m_siblings; } - QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const Q_DECL_OVERRIDE; - - // ---------------------------------------------------- - // Additional methods - void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } - NSScreen *nativeScreen() const; - void updateGeometry(); - - QPointF mapToNative(const QPointF &pos) const { return flipCoordinate(pos); } - QRectF mapToNative(const QRectF &rect) const { return flipCoordinate(rect); } - QPointF mapFromNative(const QPointF &pos) const { return flipCoordinate(pos); } - QRectF mapFromNative(const QRectF &rect) const { return flipCoordinate(rect); } - -private: - QPointF flipCoordinate(const QPointF &pos) const; - QRectF flipCoordinate(const QRectF &rect) const; +class QCocoaScreen; -public: - int m_screenIndex; - QRect m_geometry; - QRect m_availableGeometry; - QDpi m_logicalDpi; - qreal m_refreshRate; - int m_depth; - QString m_name; - QImage::Format m_format; - QSizeF m_physicalSize; - QCocoaCursor *m_cursor; - QList<QPlatformScreen *> m_siblings; -}; - -class QCocoaIntegration : public QPlatformIntegration +class QCocoaIntegration : public QObject, public QPlatformIntegration { + Q_OBJECT public: enum Option { UseFreeTypeFontEngine = 0x1 @@ -127,6 +78,7 @@ public: bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const Q_DECL_OVERRIDE; + QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; #ifndef QT_NO_OPENGL QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; #endif @@ -169,6 +121,9 @@ public: void beep() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void focusWindowChanged(QWindow *); + private: static QCocoaIntegration *mInstance; Options mOptions; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index bac49cfad9..55b3805df3 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -51,10 +51,12 @@ #include "qcocoainputcontext.h" #include "qcocoamimetypes.h" #include "qcocoaaccessibility.h" +#include "qcocoascreen.h" #include <qpa/qplatforminputcontextfactory_p.h> #include <qpa/qplatformaccessibility.h> #include <qpa/qplatforminputcontextfactory_p.h> +#include <qpa/qplatformoffscreensurface.h> #include <QtCore/qcoreapplication.h> #include <QtGui/private/qcoregraphics_p.h> @@ -78,221 +80,6 @@ QT_BEGIN_NAMESPACE class QCoreTextFontEngine; class QFontEngineFT; -QCocoaScreen::QCocoaScreen(int screenIndex) - : QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) -{ - updateGeometry(); - m_cursor = new QCocoaCursor; -} - -QCocoaScreen::~QCocoaScreen() -{ - delete m_cursor; -} - -NSScreen *QCocoaScreen::nativeScreen() const -{ - NSArray *screens = [NSScreen screens]; - - // Stale reference, screen configuration has changed - if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count]) - return nil; - - return [screens objectAtIndex:m_screenIndex]; -} - -/*! - Flips the Y coordinate of the point between quadrant I and IV. - - The native coordinate system on macOS uses quadrant I, with origin - in bottom left, and Qt uses quadrant IV, with origin in top left. - - By flippig the Y coordinate, we can map the position between the - two coordinate systems. -*/ -QPointF QCocoaScreen::flipCoordinate(const QPointF &pos) const -{ - return QPointF(pos.x(), m_geometry.height() - pos.y()); -} - -/*! - Flips the Y coordinate of the rectangle between quadrant I and IV. - - The native coordinate system on macOS uses quadrant I, with origin - in bottom left, and Qt uses quadrant IV, with origin in top left. - - By flippig the Y coordinate, we can map the rectangle between the - two coordinate systems. -*/ -QRectF QCocoaScreen::flipCoordinate(const QRectF &rect) const -{ - return QRectF(flipCoordinate(rect.topLeft() + QPoint(0, rect.height())), rect.size()); -} - -void QCocoaScreen::updateGeometry() -{ - NSScreen *nsScreen = nativeScreen(); - if (!nsScreen) - return; - - // At this point the geometry is in native coordinates, but the size - // is correct, which we take advantage of next when we map the native - // coordinates to the Qt coordinate system. - m_geometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.frame)).toRect(); - m_availableGeometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.visibleFrame)).toRect(); - - // The reference screen for the geometry is always the primary screen, but since - // we may be in the process of creating and registering the primary screen, we - // must special-case that and assign it direcly. - QCocoaScreen *primaryScreen = (nsScreen == [[NSScreen screens] firstObject]) ? - this : static_cast<QCocoaScreen*>(QGuiApplication::primaryScreen()->handle()); - - m_geometry = primaryScreen->mapFromNative(m_geometry).toRect(); - m_availableGeometry = primaryScreen->mapFromNative(m_availableGeometry).toRect(); - - m_format = QImage::Format_RGB32; - m_depth = NSBitsPerPixelFromDepth([nsScreen depth]); - - NSDictionary *devDesc = [nsScreen deviceDescription]; - CGDirectDisplayID dpy = [[devDesc objectForKey:@"NSScreenNumber"] unsignedIntValue]; - CGSize size = CGDisplayScreenSize(dpy); - m_physicalSize = QSizeF(size.width, size.height); - m_logicalDpi.first = 72; - m_logicalDpi.second = 72; - CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(dpy); - float refresh = CGDisplayModeGetRefreshRate(displayMode); - CGDisplayModeRelease(displayMode); - if (refresh > 0) - m_refreshRate = refresh; - - // Get m_name (brand/model of the monitor) - NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(dpy), kIODisplayOnlyPreferredName); - NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; - if ([localizedNames count] > 0) - m_name = QString::fromUtf8([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]); - [deviceInfo release]; - - QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); - QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); - QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); -} - -qreal QCocoaScreen::devicePixelRatio() const -{ - QMacAutoReleasePool pool; - NSScreen *nsScreen = nativeScreen(); - return qreal(nsScreen ? [nsScreen backingScaleFactor] : 1.0); -} - -QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const -{ - QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); - if (type == QPlatformScreen::Subpixel_None) { - // Every OSX machine has RGB pixels unless a peculiar or rotated non-Apple screen is attached - type = QPlatformScreen::Subpixel_RGB; - } - return type; -} - -QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const -{ - NSPoint screenPoint = qt_mac_flipPoint(point); - - // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint: - // belowWindowWithWindowNumber] may return windows that are not interesting - // to Qt. The search iterates until a suitable window or no window is found. - NSInteger topWindowNumber = 0; - QWindow *window = 0; - do { - // Get the top-most window, below any previously rejected window. - topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint - belowWindowWithWindowNumber:topWindowNumber]; - - // Continue the search if the window does not belong to this process. - NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber]; - if (nsWindow == 0) - continue; - - // Continue the search if the window does not belong to Qt. - if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) - continue; - - id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow); - QCocoaWindow *cocoaWindow = proto.helper.platformWindow; - if (!cocoaWindow) - continue; - window = cocoaWindow->window(); - - // Continue the search if the window is not a top-level window. - if (!window->isTopLevel()) - continue; - - // Stop searching. The current window is the correct window. - break; - } while (topWindowNumber > 0); - - return window; -} - -QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const -{ - // TODO window should be handled - Q_UNUSED(window) - - const int maxDisplays = 128; // 128 displays should be enough for everyone. - CGDirectDisplayID displays[maxDisplays]; - CGDisplayCount displayCount; - CGRect cgRect; - - if (width < 0 || height < 0) { - // get all displays - cgRect = CGRectInfinite; - } else { - cgRect = CGRectMake(x, y, width, height); - } - const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); - - if (err && displayCount == 0) - return QPixmap(); - - // calculate pixmap size - QSize windowSize(width, height); - if (width < 0 || height < 0) { - QRect windowRect; - for (uint i = 0; i < displayCount; ++i) { - const CGRect cgRect = CGDisplayBounds(displays[i]); - QRect qRect(cgRect.origin.x, cgRect.origin.y, cgRect.size.width, cgRect.size.height); - windowRect = windowRect.united(qRect); - } - if (width < 0) - windowSize.setWidth(windowRect.width()); - if (height < 0) - windowSize.setHeight(windowRect.height()); - } - - QPixmap windowPixmap(windowSize * devicePixelRatio()); - windowPixmap.fill(Qt::transparent); - - for (uint i = 0; i < displayCount; ++i) { - const CGRect bounds = CGDisplayBounds(displays[i]); - int w = (width < 0 ? bounds.size.width : width) * devicePixelRatio(); - int h = (height < 0 ? bounds.size.height : height) * devicePixelRatio(); - QRect displayRect = QRect(x, y, w, h); - displayRect = displayRect.translated(qRound(-bounds.origin.x), qRound(-bounds.origin.y)); - QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displays[i], - CGRectMake(displayRect.x(), displayRect.y(), displayRect.width(), displayRect.height())); - QPixmap pix(w, h); - pix.fill(Qt::transparent); - CGRect rect = CGRectMake(0, 0, w, h); - QMacCGContext ctx(&pix); - qt_mac_drawCGImage(ctx, &rect, image); - - QPainter painter(&windowPixmap); - painter.drawPixmap(0, 0, pix); - } - return windowPixmap; -} - static QCocoaIntegration::Options parseOptions(const QStringList ¶mList) { QCocoaIntegration::Options options; @@ -355,7 +142,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) // Move the application window to front to make it take focus, also when launching // from the terminal. On 10.12+ this call has been moved to applicationDidFinishLauching // to work around issues with loss of focus at startup. - if (QSysInfo::macVersion() < QSysInfo::MV_10_12) { + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSSierra) { // Ignoring other apps is necessary (we must ignore the terminal), but makes // Qt apps play slightly less nice with other apps when lanching from Finder // (See the activateIgnoringOtherApps docs.) @@ -392,6 +179,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) QMacInternalPasteboardMime::initializeMimeTypes(); QCocoaMimeTypes::initializeMimeTypes(); QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + + connect(qGuiApp, &QGuiApplication::focusWindowChanged, + this, &QCocoaIntegration::focusWindowChanged); } QCocoaIntegration::~QCocoaIntegration() @@ -435,6 +225,8 @@ QCocoaIntegration::Options QCocoaIntegration::options() const return mOptions; } +Q_LOGGING_CATEGORY(lcCocoaScreen, "qt.qpa.cocoa.screens"); + /*! \brief Synchronizes the screen list, adds new screens, removes deleted ones */ @@ -474,9 +266,11 @@ void QCocoaIntegration::updateScreens() if (screen) { remainingScreens.remove(screen); screen->updateGeometry(); + qCDebug(lcCocoaScreen) << "Updated properties of" << screen; } else { screen = new QCocoaScreen(i); mScreens.append(screen); + qCDebug(lcCocoaScreen) << "Adding" << screen; screenAdded(screen); } siblings << screen; @@ -493,6 +287,7 @@ void QCocoaIntegration::updateScreens() mScreens.removeOne(screen); // Prevent stale references to NSScreen during destroy screen->m_screenIndex = -1; + qCDebug(lcCocoaScreen) << "Removing" << screen; destroyScreen(screen); } } @@ -545,6 +340,24 @@ QPlatformWindow *QCocoaIntegration::createForeignWindow(QWindow *window, WId nat return new QCocoaWindow(window, nativeHandle); } +class QCocoaOffscreenSurface : public QPlatformOffscreenSurface +{ +public: + QCocoaOffscreenSurface(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) {} + + QSurfaceFormat format() const override + { + Q_ASSERT(offscreenSurface()); + return offscreenSurface()->requestedFormat(); + } + bool isValid() const override { return true; } +}; + +QPlatformOffscreenSurface *QCocoaIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const +{ + return new QCocoaOffscreenSurface(surface); +} + #ifndef QT_NO_OPENGL QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { @@ -700,4 +513,33 @@ void QCocoaIntegration::beep() const NSBeep(); } +void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow) +{ + // Don't revert icon just because we lost focus + if (!focusWindow) + return; + + static bool hasDefaultApplicationIcon = [](){ + NSImage *genericApplicationIcon = [[NSWorkspace sharedWorkspace] + iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; + NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon]; + + NSRect rect = NSMakeRect(0, 0, 32, 32); + return [applicationIcon CGImageForProposedRect:&rect context:nil hints:nil] + == [genericApplicationIcon CGImageForProposedRect:&rect context:nil hints:nil]; + }(); + + // Don't let the window icon override an explicit application icon set in the Info.plist + if (!hasDefaultApplicationIcon) + return; + + // Or an explicit application icon set on QGuiApplication + if (!qGuiApp->windowIcon().isNull()) + return; + + setApplicationIcon(focusWindow->icon()); +} + +#include "moc_qcocoaintegration.cpp" + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.h b/src/plugins/platforms/cocoa/qcocoakeymapper.h index 4ba615efeb..a75e275077 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.h +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.h @@ -91,8 +91,6 @@ public: private: QCFType<TISInputSourceRef> currentInputSource; - QLocale keyboardInputLocale; - Qt::LayoutDirection keyboardInputDirection; enum { NullMode, UnicodeMode, OtherMode } keyboard_mode; union { const UCKeyboardLayout *unicode; diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index 1ef7f11011..80140505d1 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm @@ -72,14 +72,6 @@ static const Qt::KeyboardModifiers ModsTbl[] = { bool qt_mac_eat_unicode_key = false; -Q_GUI_EXPORT void qt_mac_secure_keyboard(bool b) -{ - static bool secure = false; - if (b != secure){ - b ? EnableSecureEventInput() : DisableSecureEventInput(); - secure = b; - } -} /* key maps */ struct qt_mac_enum_mapper @@ -384,18 +376,7 @@ bool QCocoaKeyMapper::updateKeyboard() } currentInputSource = source; keyboard_dead = 0; - CFStringRef iso639Code; - CFArrayRef array = static_cast<CFArrayRef>(TISGetInputSourceProperty(currentInputSource, kTISPropertyInputSourceLanguages)); - iso639Code = static_cast<CFStringRef>(CFArrayGetValueAtIndex(array, 0)); // Actually a RFC3066bis, but it's close enough - - if (iso639Code) { - keyboardInputLocale = QLocale(QString::fromCFString(iso639Code)); - keyboardInputDirection = keyboardInputLocale.textDirection(); - } else { - keyboardInputLocale = QLocale::c(); - keyboardInputDirection = Qt::LeftToRight; - } return true; } diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h index 7baaf971f4..5081fc78c6 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.h +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -55,11 +55,6 @@ public: QCocoaMenu(); ~QCocoaMenu(); - void setTag(quintptr tag) Q_DECL_OVERRIDE - { m_tag = tag; } - quintptr tag() const Q_DECL_OVERRIDE - { return m_tag; } - void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE; void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; void syncMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; @@ -108,7 +103,6 @@ private: QList<QCocoaMenuItem *> m_menuItems; NSMenu *m_nativeMenu; NSMenuItem *m_attachedItem; - quintptr m_tag; int m_updateTimer; bool m_enabled:1; bool m_parentEnabled:1; diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 8bdd0512de..1345295ef5 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -253,13 +253,24 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaMenuDelegate); return nil; } +// Cocoa will query the menu item's target for the worksWhenModal selector. +// So we need to implement this to allow the items to be handled correctly +// when a modal dialog is visible. +- (BOOL)worksWhenModal +{ + if (!QGuiApplication::modalWindow()) + return YES; + if (auto *mb = qobject_cast<QCocoaMenuBar *>(m_menu->menuParent())) + return QGuiApplication::modalWindow()->handle() == mb->cocoaWindow() ? YES : NO; + return YES; +} + @end QT_BEGIN_NAMESPACE QCocoaMenu::QCocoaMenu() : m_attachedItem(0), - m_tag(0), m_updateTimer(0), m_enabled(true), m_parentEnabled(true), diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h index a4ee531e91..a259147247 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.h +++ b/src/plugins/platforms/cocoa/qcocoamenubar.h @@ -71,6 +71,7 @@ public: QList<QCocoaMenuItem*> merged() const; NSMenuItem *itemForRole(QPlatformMenuItem::MenuRole r); + QCocoaWindow *cocoaWindow() const; void syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate); diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm index a4cd465dae..cf9553bcf7 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.mm +++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm @@ -452,5 +452,10 @@ NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole r) return Q_NULLPTR; } +QCocoaWindow *QCocoaMenuBar::cocoaWindow() const +{ + return m_window.data(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h index 23f788687c..53862d0484 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -78,11 +78,6 @@ public: QCocoaMenuItem(); ~QCocoaMenuItem(); - void setTag(quintptr tag) Q_DECL_OVERRIDE - { m_tag = tag; } - quintptr tag() const Q_DECL_OVERRIDE - { return m_tag; } - void setText(const QString &text) Q_DECL_OVERRIDE; void setIcon(const QIcon &icon) Q_DECL_OVERRIDE; void setMenu(QPlatformMenu *menu) Q_DECL_OVERRIDE; @@ -129,7 +124,6 @@ private: #ifndef QT_NO_SHORTCUT QKeySequence m_shortcut; #endif - quintptr m_tag; int m_iconSize; bool m_textSynced:1; bool m_isVisible:1; diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index 394e0fb8e4..eaf310ec51 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -95,7 +95,6 @@ QCocoaMenuItem::QCocoaMenuItem() : m_itemView(nil), m_menu(NULL), m_role(NoRole), - m_tag(0), m_iconSize(16), m_textSynced(false), m_isVisible(true), diff --git a/src/plugins/platforms/cocoa/qcocoamimetypes.mm b/src/plugins/platforms/cocoa/qcocoamimetypes.mm index 093f86da6e..f7662a92a4 100644 --- a/src/plugins/platforms/cocoa/qcocoamimetypes.mm +++ b/src/plugins/platforms/cocoa/qcocoamimetypes.mm @@ -105,101 +105,9 @@ QList<QByteArray> QMacPasteboardMimeTraditionalMacPlainText::convertFromMime(con return ret; } -class QMacPasteboardMimeTiff : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeTiff() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString QMacPasteboardMimeTiff::convertorName() -{ - return QLatin1String("Tiff"); -} - -QString QMacPasteboardMimeTiff::flavorFor(const QString &mime) -{ - if (mime.startsWith(QLatin1String("application/x-qt-image"))) - return QLatin1String("public.tiff"); - return QString(); -} - -QString QMacPasteboardMimeTiff::mimeFor(QString flav) -{ - if (flav == QLatin1String("public.tiff")) - return QLatin1String("application/x-qt-image"); - return QString(); -} - -bool QMacPasteboardMimeTiff::canConvert(const QString &mime, QString flav) -{ - return flav == QLatin1String("public.tiff") && mime == QLatin1String("application/x-qt-image"); -} - -QVariant QMacPasteboardMimeTiff::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) -{ - if (data.count() > 1) - qWarning("QMacPasteboardMimeTiff: Cannot handle multiple member data"); - QVariant ret; - if (!canConvert(mime, flav)) - return ret; - const QByteArray &a = data.first(); - QCFType<CGImageRef> image; - QCFType<CFDataRef> tiffData = CFDataCreateWithBytesNoCopy(0, - reinterpret_cast<const UInt8 *>(a.constData()), - a.size(), kCFAllocatorNull); - QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0); - image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); - if (image != 0) - ret = QVariant(qt_mac_toQImage(image)); - return ret; -} - -QList<QByteArray> QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav) -{ - QList<QByteArray> ret; - if (!canConvert(mime, flav)) - return ret; - - QImage img = qvariant_cast<QImage>(variant); - QCFType<CGImageRef> cgimage = qt_mac_toCGImage(img); - - QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0); - QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0); - if (imageDestination != 0) { - CFTypeRef keys[2]; - QCFType<CFTypeRef> values[2]; - QCFType<CFDictionaryRef> options; - keys[0] = kCGImagePropertyPixelWidth; - keys[1] = kCGImagePropertyPixelHeight; - int width = img.width(); - int height = img.height(); - values[0] = CFNumberCreate(0, kCFNumberIntType, &width); - values[1] = CFNumberCreate(0, kCFNumberIntType, &height); - options = CFDictionaryCreate(0, reinterpret_cast<const void **>(keys), - reinterpret_cast<const void **>(values), 2, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CGImageDestinationAddImage(imageDestination, cgimage, options); - CGImageDestinationFinalize(imageDestination); - } - QByteArray ar(CFDataGetLength(data), 0); - CFDataGetBytes(data, - CFRangeMake(0, ar.size()), - reinterpret_cast<UInt8 *>(ar.data())); - ret.append(ar); - return ret; -} - void QCocoaMimeTypes::initializeMimeTypes() { new QMacPasteboardMimeTraditionalMacPlainText; - new QMacPasteboardMimeTiff; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 26ab07ffaf..8943cb6cd5 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -103,7 +103,7 @@ void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceS return static_cast<QCocoaWindow *>(window->handle())->currentContext()->nsOpenGLContext(); #endif } else if (resourceString == "nswindow") { - return static_cast<QCocoaWindow *>(window->handle())->m_nsWindow; + return static_cast<QCocoaWindow *>(window->handle())->nativeWindow(); } return 0; } diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h new file mode 100644 index 0000000000..937002f493 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoascreen.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QCOCOASCREEN_H +#define QCOCOASCREEN_H + +#include <AppKit/AppKit.h> + +#include "qcocoacursor.h" + +#include <qpa/qplatformintegration.h> + +QT_BEGIN_NAMESPACE + +class QCocoaScreen : public QPlatformScreen +{ +public: + QCocoaScreen(int screenIndex); + ~QCocoaScreen(); + + // ---------------------------------------------------- + // Virtual methods overridden from QPlatformScreen + QPixmap grabWindow(WId window, int x, int y, int width, int height) const Q_DECL_OVERRIDE; + QRect geometry() const Q_DECL_OVERRIDE { return m_geometry; } + QRect availableGeometry() const Q_DECL_OVERRIDE { return m_availableGeometry; } + int depth() const Q_DECL_OVERRIDE { return m_depth; } + QImage::Format format() const Q_DECL_OVERRIDE { return m_format; } + qreal devicePixelRatio() const Q_DECL_OVERRIDE; + QSizeF physicalSize() const Q_DECL_OVERRIDE { return m_physicalSize; } + QDpi logicalDpi() const Q_DECL_OVERRIDE { return m_logicalDpi; } + qreal refreshRate() const Q_DECL_OVERRIDE { return m_refreshRate; } + QString name() const Q_DECL_OVERRIDE { return m_name; } + QPlatformCursor *cursor() const Q_DECL_OVERRIDE { return m_cursor; } + QWindow *topLevelAt(const QPoint &point) const Q_DECL_OVERRIDE; + QList<QPlatformScreen *> virtualSiblings() const Q_DECL_OVERRIDE { return m_siblings; } + QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const Q_DECL_OVERRIDE; + + // ---------------------------------------------------- + // Additional methods + void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } + NSScreen *nativeScreen() const; + void updateGeometry(); + + QPointF mapToNative(const QPointF &pos) const { return flipCoordinate(pos); } + QRectF mapToNative(const QRectF &rect) const { return flipCoordinate(rect); } + QPointF mapFromNative(const QPointF &pos) const { return flipCoordinate(pos); } + QRectF mapFromNative(const QRectF &rect) const { return flipCoordinate(rect); } + + static QCocoaScreen *primaryScreen(); + +private: + QPointF flipCoordinate(const QPointF &pos) const; + QRectF flipCoordinate(const QRectF &rect) const; + +public: + int m_screenIndex; + QRect m_geometry; + QRect m_availableGeometry; + QDpi m_logicalDpi; + qreal m_refreshRate; + int m_depth; + QString m_name; + QImage::Format m_format; + QSizeF m_physicalSize; + QCocoaCursor *m_cursor; + QList<QPlatformScreen *> m_siblings; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaScreen *screen); +#endif + +QT_END_NAMESPACE + +#endif + diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm new file mode 100644 index 0000000000..f523873bde --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#include "qcocoascreen.h" + +#include "qcocoawindow.h" +#include "qcocoahelpers.h" + +#include <QtCore/qcoreapplication.h> +#include <QtGui/private/qcoregraphics_p.h> + +#include <IOKit/graphics/IOGraphicsLib.h> + +QT_BEGIN_NAMESPACE + +class QCoreTextFontEngine; +class QFontEngineFT; + +QCocoaScreen::QCocoaScreen(int screenIndex) + : QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) +{ + updateGeometry(); + m_cursor = new QCocoaCursor; +} + +QCocoaScreen::~QCocoaScreen() +{ + delete m_cursor; +} + +NSScreen *QCocoaScreen::nativeScreen() const +{ + NSArray *screens = [NSScreen screens]; + + // Stale reference, screen configuration has changed + if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count]) + return nil; + + return [screens objectAtIndex:m_screenIndex]; +} + +/*! + Flips the Y coordinate of the point between quadrant I and IV. + + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. + + By flippig the Y coordinate, we can map the position between the + two coordinate systems. +*/ +QPointF QCocoaScreen::flipCoordinate(const QPointF &pos) const +{ + return QPointF(pos.x(), m_geometry.height() - pos.y()); +} + +/*! + Flips the Y coordinate of the rectangle between quadrant I and IV. + + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. + + By flippig the Y coordinate, we can map the rectangle between the + two coordinate systems. +*/ +QRectF QCocoaScreen::flipCoordinate(const QRectF &rect) const +{ + return QRectF(flipCoordinate(rect.topLeft() + QPoint(0, rect.height())), rect.size()); +} + +void QCocoaScreen::updateGeometry() +{ + NSScreen *nsScreen = nativeScreen(); + if (!nsScreen) + return; + + // At this point the geometry is in native coordinates, but the size + // is correct, which we take advantage of next when we map the native + // coordinates to the Qt coordinate system. + m_geometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.frame)).toRect(); + m_availableGeometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.visibleFrame)).toRect(); + + // The reference screen for the geometry is always the primary screen, but since + // we may be in the process of creating and registering the primary screen, we + // must special-case that and assign it direcly. + QCocoaScreen *primaryScreen = (nsScreen == [[NSScreen screens] firstObject]) ? + this : QCocoaScreen::primaryScreen(); + + m_geometry = primaryScreen->mapFromNative(m_geometry).toRect(); + m_availableGeometry = primaryScreen->mapFromNative(m_availableGeometry).toRect(); + + m_format = QImage::Format_RGB32; + m_depth = NSBitsPerPixelFromDepth([nsScreen depth]); + + NSDictionary *devDesc = [nsScreen deviceDescription]; + CGDirectDisplayID dpy = [[devDesc objectForKey:@"NSScreenNumber"] unsignedIntValue]; + CGSize size = CGDisplayScreenSize(dpy); + m_physicalSize = QSizeF(size.width, size.height); + m_logicalDpi.first = 72; + m_logicalDpi.second = 72; + CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(dpy); + float refresh = CGDisplayModeGetRefreshRate(displayMode); + CGDisplayModeRelease(displayMode); + if (refresh > 0) + m_refreshRate = refresh; + + // Get m_name (brand/model of the monitor) + NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(dpy), kIODisplayOnlyPreferredName); + NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; + if ([localizedNames count] > 0) + m_name = QString::fromUtf8([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]); + [deviceInfo release]; + + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); + QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); + QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); +} + +qreal QCocoaScreen::devicePixelRatio() const +{ + QMacAutoReleasePool pool; + NSScreen *nsScreen = nativeScreen(); + return qreal(nsScreen ? [nsScreen backingScaleFactor] : 1.0); +} + +QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const +{ + QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); + if (type == QPlatformScreen::Subpixel_None) { + // Every OSX machine has RGB pixels unless a peculiar or rotated non-Apple screen is attached + type = QPlatformScreen::Subpixel_RGB; + } + return type; +} + +QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const +{ + NSPoint screenPoint = qt_mac_flipPoint(point); + + // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint: + // belowWindowWithWindowNumber] may return windows that are not interesting + // to Qt. The search iterates until a suitable window or no window is found. + NSInteger topWindowNumber = 0; + QWindow *window = 0; + do { + // Get the top-most window, below any previously rejected window. + topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint + belowWindowWithWindowNumber:topWindowNumber]; + + // Continue the search if the window does not belong to this process. + NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber]; + if (nsWindow == 0) + continue; + + // Continue the search if the window does not belong to Qt. + if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) + continue; + + id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow); + QCocoaWindow *cocoaWindow = proto.platformWindow; + if (!cocoaWindow) + continue; + window = cocoaWindow->window(); + + // Continue the search if the window is not a top-level window. + if (!window->isTopLevel()) + continue; + + // Stop searching. The current window is the correct window. + break; + } while (topWindowNumber > 0); + + return window; +} + +QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const +{ + // TODO window should be handled + Q_UNUSED(window) + + const int maxDisplays = 128; // 128 displays should be enough for everyone. + CGDirectDisplayID displays[maxDisplays]; + CGDisplayCount displayCount; + CGRect cgRect; + + if (width < 0 || height < 0) { + // get all displays + cgRect = CGRectInfinite; + } else { + cgRect = CGRectMake(x, y, width, height); + } + const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); + + if (err && displayCount == 0) + return QPixmap(); + + // calculate pixmap size + QSize windowSize(width, height); + if (width < 0 || height < 0) { + QRect windowRect; + for (uint i = 0; i < displayCount; ++i) { + const CGRect cgRect = CGDisplayBounds(displays[i]); + QRect qRect(cgRect.origin.x, cgRect.origin.y, cgRect.size.width, cgRect.size.height); + windowRect = windowRect.united(qRect); + } + if (width < 0) + windowSize.setWidth(windowRect.width()); + if (height < 0) + windowSize.setHeight(windowRect.height()); + } + + QPixmap windowPixmap(windowSize * devicePixelRatio()); + windowPixmap.fill(Qt::transparent); + + for (uint i = 0; i < displayCount; ++i) { + const CGRect bounds = CGDisplayBounds(displays[i]); + int w = (width < 0 ? bounds.size.width : width) * devicePixelRatio(); + int h = (height < 0 ? bounds.size.height : height) * devicePixelRatio(); + QRect displayRect = QRect(x, y, w, h); + displayRect = displayRect.translated(qRound(-bounds.origin.x), qRound(-bounds.origin.y)); + QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displays[i], + CGRectMake(displayRect.x(), displayRect.y(), displayRect.width(), displayRect.height())); + QPixmap pix(w, h); + pix.fill(Qt::transparent); + CGRect rect = CGRectMake(0, 0, w, h); + QMacCGContext ctx(&pix); + qt_mac_drawCGImage(ctx, &rect, image); + + QPainter painter(&windowPixmap); + painter.drawPixmap(0, 0, pix); + } + return windowPixmap; +} + +/*! + The screen used as a reference for global window geometry +*/ +QCocoaScreen *QCocoaScreen::primaryScreen() +{ + return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle()); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaScreen *screen) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "QCocoaScreen(" << (const void *)screen; + if (screen) { + debug << ", index=" << screen->m_screenIndex; + debug << ", native=" << screen->nativeScreen(); + debug << ", geometry=" << screen->geometry(); + debug << ", dpr=" << screen->devicePixelRatio(); + debug << ", name=" << screen->name(); + } + debug << ')'; + return debug; +} +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm index 9ddad7fc7a..7c6f879b18 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -45,49 +45,8 @@ #include <QtGui/qfont.h> #include <QtGui/private/qcoregraphics_p.h> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE -QBrush qt_mac_brushForTheme(ThemeBrush brush) -{ - QMacAutoReleasePool pool; - - QCFType<CGColorRef> cgClr = 0; - HIThemeBrushCreateCGColor(brush, &cgClr); - return qt_mac_toQBrush(cgClr); -} - -QColor qt_mac_colorForThemeTextColor(ThemeTextColor themeColor) -{ - // No GetThemeTextColor in 64-bit mode, use hardcoded values: - switch (themeColor) { - case kThemeTextColorAlertActive: - case kThemeTextColorTabFrontActive: - case kThemeTextColorBevelButtonActive: - case kThemeTextColorListView: - case kThemeTextColorPlacardActive: - case kThemeTextColorPopupButtonActive: - case kThemeTextColorPopupLabelActive: - case kThemeTextColorPushButtonActive: - return Qt::black; - case kThemeTextColorAlertInactive: - case kThemeTextColorDialogInactive: - case kThemeTextColorPlacardInactive: - case kThemeTextColorPopupButtonInactive: - case kThemeTextColorPopupLabelInactive: - case kThemeTextColorPushButtonInactive: - case kThemeTextColorTabFrontInactive: - case kThemeTextColorBevelButtonInactive: - case kThemeTextColorMenuItemDisabled: - return QColor(127, 127, 127, 255); - case kThemeTextColorMenuItemSelected: - return Qt::white; - default: - return QColor(0, 0, 0, 255); // ### TODO: Sample color like Qt 4. - } -} - QPalette * qt_mac_createSystemPalette() { QColor qc; @@ -119,7 +78,7 @@ QPalette * qt_mac_createSystemPalette() palette->setBrush(QPalette::Shadow, background.darker(170)); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorDialogActive); + qc = qt_mac_toQColor([NSColor controlTextColor]); palette->setColor(QPalette::Active, QPalette::Text, qc); palette->setColor(QPalette::Active, QPalette::WindowText, qc); palette->setColor(QPalette::Active, QPalette::HighlightedText, qc); @@ -127,7 +86,7 @@ QPalette * qt_mac_createSystemPalette() palette->setColor(QPalette::Inactive, QPalette::WindowText, qc); palette->setColor(QPalette::Inactive, QPalette::HighlightedText, qc); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorDialogInactive); + qc = qt_mac_toQColor([NSColor disabledControlTextColor]); palette->setColor(QPalette::Disabled, QPalette::Text, qc); palette->setColor(QPalette::Disabled, QPalette::WindowText, qc); palette->setColor(QPalette::Disabled, QPalette::HighlightedText, qc); @@ -136,60 +95,66 @@ QPalette * qt_mac_createSystemPalette() } struct QMacPaletteMap { - inline QMacPaletteMap(QPlatformTheme::Palette p, ThemeBrush a, ThemeBrush i) : + inline QMacPaletteMap(QPlatformTheme::Palette p, NSColor *a, NSColor *i) : paletteRole(p), active(a), inactive(i) { } QPlatformTheme::Palette paletteRole; - ThemeBrush active, inactive; + NSColor *active, *inactive; }; +#define MAC_PALETTE_ENTRY(pal, active, inactive) \ + QMacPaletteMap(pal, [NSColor active], [NSColor inactive]) static QMacPaletteMap mac_widget_colors[] = { - QMacPaletteMap(QPlatformTheme::ToolButtonPalette, kThemeTextColorBevelButtonActive, kThemeTextColorBevelButtonInactive), - QMacPaletteMap(QPlatformTheme::ButtonPalette, kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), - QMacPaletteMap(QPlatformTheme::HeaderPalette, kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), - QMacPaletteMap(QPlatformTheme::ComboBoxPalette, kThemeTextColorPopupButtonActive, kThemeTextColorPopupButtonInactive), - QMacPaletteMap(QPlatformTheme::ItemViewPalette, kThemeTextColorListView, kThemeTextColorDialogInactive), - QMacPaletteMap(QPlatformTheme::MessageBoxLabelPalette, kThemeTextColorAlertActive, kThemeTextColorAlertInactive), - QMacPaletteMap(QPlatformTheme::TabBarPalette, kThemeTextColorTabFrontActive, kThemeTextColorTabFrontInactive), - QMacPaletteMap(QPlatformTheme::LabelPalette, kThemeTextColorPlacardActive, kThemeTextColorPlacardInactive), - QMacPaletteMap(QPlatformTheme::GroupBoxPalette, kThemeTextColorPlacardActive, kThemeTextColorPlacardInactive), - QMacPaletteMap(QPlatformTheme::MenuPalette, kThemeTextColorMenuItemActive, kThemeTextColorMenuItemDisabled), - QMacPaletteMap(QPlatformTheme::MenuBarPalette, kThemeTextColorMenuItemActive, kThemeTextColorMenuItemDisabled), - //### TODO: The zeros below gives white-on-black text. - QMacPaletteMap(QPlatformTheme::TextEditPalette, 0, 0), - QMacPaletteMap(QPlatformTheme::TextLineEditPalette, 0, 0), - QMacPaletteMap(QPlatformTheme::NPalettes, 0, 0) }; + MAC_PALETTE_ENTRY(QPlatformTheme::ToolButtonPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::ButtonPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::HeaderPalette, headerTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::ComboBoxPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::ItemViewPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::MessageBoxLabelPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::TabBarPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::LabelPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::GroupBoxPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::MenuPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::MenuBarPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::TextEditPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::TextLineEditPalette, textColor, disabledControlTextColor) +}; +#undef MAC_PALETTE_ENTRY + +static const int mac_widget_colors_count = sizeof(mac_widget_colors) / sizeof(mac_widget_colors[0]); QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() { QHash<QPlatformTheme::Palette, QPalette*> palettes; QColor qc; - for (int i = 0; mac_widget_colors[i].paletteRole != QPlatformTheme::NPalettes; i++) { + for (int i = 0; i < mac_widget_colors_count; i++) { QPalette &pal = *qt_mac_createSystemPalette(); if (mac_widget_colors[i].active != 0) { - qc = qt_mac_colorForThemeTextColor(mac_widget_colors[i].active); + qc = qt_mac_toQColor(mac_widget_colors[i].active); pal.setColor(QPalette::Active, QPalette::Text, qc); pal.setColor(QPalette::Inactive, QPalette::Text, qc); pal.setColor(QPalette::Active, QPalette::WindowText, qc); pal.setColor(QPalette::Inactive, QPalette::WindowText, qc); pal.setColor(QPalette::Active, QPalette::HighlightedText, qc); pal.setColor(QPalette::Inactive, QPalette::HighlightedText, qc); - qc = qt_mac_colorForThemeTextColor(mac_widget_colors[i].inactive); + qc = qt_mac_toQColor(mac_widget_colors[i].inactive); pal.setColor(QPalette::Disabled, QPalette::Text, qc); pal.setColor(QPalette::Disabled, QPalette::WindowText, qc); pal.setColor(QPalette::Disabled, QPalette::HighlightedText, qc); } if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette || mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) { - pal.setBrush(QPalette::Background, qt_mac_brushForTheme(kThemeBrushMenuBackground)); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorMenuItemActive); + pal.setBrush(QPalette::Highlight, qt_mac_toQColor([NSColor selectedMenuItemColor])); + qc = qt_mac_toQColor([NSColor labelColor]); pal.setBrush(QPalette::ButtonText, qc); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorMenuItemSelected); + pal.setBrush(QPalette::Text, qc); + qc = qt_mac_toQColor([NSColor selectedMenuItemTextColor]); pal.setBrush(QPalette::HighlightedText, qc); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorMenuItemDisabled); + qc = qt_mac_toQColor([NSColor disabledControlTextColor]); pal.setBrush(QPalette::Disabled, QPalette::Text, qc); } else if ((mac_widget_colors[i].paletteRole == QPlatformTheme::ButtonPalette) - || (mac_widget_colors[i].paletteRole == QPlatformTheme::HeaderPalette)) { + || (mac_widget_colors[i].paletteRole == QPlatformTheme::HeaderPalette) + || (mac_widget_colors[i].paletteRole == QPlatformTheme::TabBarPalette)) { pal.setColor(QPalette::Disabled, QPalette::ButtonText, pal.color(QPalette::Disabled, QPalette::Text)); pal.setColor(QPalette::Inactive, QPalette::ButtonText, diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index a1c1b379f7..5239864ad8 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -55,6 +55,7 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/qpainter.h> +#include <QtGui/qtextformat.h> #include <QtFontDatabaseSupport/private/qcoretextfontdatabase_p.h> #include <QtThemeSupport/private/qabstractfileiconengine_p.h> #include <qpa/qplatformdialoghelper.h> @@ -341,9 +342,11 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const case IconPixmapSizes: return QVariant::fromValue(QCocoaFileIconEngine::availableIconSizes()); case QPlatformTheme::PasswordMaskCharacter: - return QVariant(QChar(kBulletUnicode)); + return QVariant(QChar(0x2022)); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::SpellCheckUnderlineStyle: + return QVariant(int(QTextCharFormat::DotLine)); default: break; } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index deba861fcc..6fbe29f683 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -50,79 +50,15 @@ #include "qcocoaglcontext.h" #endif #include "qnsview.h" +#include "qnswindow.h" #include "qt_mac_p.h" -QT_FORWARD_DECLARE_CLASS(QCocoaWindow) - -@class QT_MANGLE_NAMESPACE(QNSWindowHelper); - -@protocol QNSWindowProtocol - -@property (nonatomic, readonly) QT_MANGLE_NAMESPACE(QNSWindowHelper) *helper; - -- (void)superSendEvent:(NSEvent *)theEvent; -- (void)closeAndRelease; - -@end - -typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow; - -@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject -{ - QCocoaNSWindow *_window; - QPointer<QCocoaWindow> _platformWindow; - BOOL _grabbingMouse; - BOOL _releaseOnMouseUp; -} - -@property (nonatomic, readonly) QCocoaNSWindow *window; -@property (nonatomic, readonly) QCocoaWindow *platformWindow; -@property (nonatomic) BOOL grabbingMouse; -@property (nonatomic) BOOL releaseOnMouseUp; - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow; -- (void)handleWindowEvent:(NSEvent *)theEvent; -- (void) clearWindow; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper); - -@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol> -{ - QNSWindowHelper *_helper; -} - -@property (nonatomic, readonly) QNSWindowHelper *helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); - -@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> -{ - QNSWindowHelper *_helper; -} - -@property (nonatomic, readonly) QNSWindowHelper *helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); +QT_BEGIN_NAMESPACE -@class QT_MANGLE_NAMESPACE(QNSWindowDelegate); +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +#endif -QT_BEGIN_NAMESPACE // QCocoaWindow // // QCocoaWindow is an NSView (not an NSWindow!) in the sense @@ -159,16 +95,15 @@ public: QCocoaWindow(QWindow *tlw, WId nativeHandle = 0); ~QCocoaWindow(); + void initialize() override; + void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; QRect geometry() const Q_DECL_OVERRIDE; void setCocoaGeometry(const QRect &rect); - void clipChildWindows(); - void clipWindow(const NSRect &clipRect); - void show(bool becauseOfAncestor = false); - void hide(bool becauseOfAncestor = false); + void setVisible(bool visible) Q_DECL_OVERRIDE; void setWindowFlags(Qt::WindowFlags flags) Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; + void setWindowState(Qt::WindowStates state) Q_DECL_OVERRIDE; void setWindowTitle(const QString &title) Q_DECL_OVERRIDE; void setWindowFilePath(const QString &filePath) Q_DECL_OVERRIDE; void setWindowIcon(const QIcon &icon) Q_DECL_OVERRIDE; @@ -188,6 +123,7 @@ public: bool isForeignWindow() const Q_DECL_OVERRIDE; + void requestUpdate() override; void requestActivateWindow() Q_DECL_OVERRIDE; WId winId() const Q_DECL_OVERRIDE; @@ -198,11 +134,12 @@ public: void setEmbeddedInForeignView(bool subwindow); + Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); + Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); + Q_NOTIFICATION_HANDLER(NSWindowWillMoveNotification) void windowWillMove(); Q_NOTIFICATION_HANDLER(NSWindowDidMoveNotification) void windowDidMove(); Q_NOTIFICATION_HANDLER(NSWindowDidResizeNotification) void windowDidResize(); - Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); - Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); Q_NOTIFICATION_HANDLER(NSWindowDidEndLiveResizeNotification) void windowDidEndLiveResize(); Q_NOTIFICATION_HANDLER(NSWindowDidBecomeKeyNotification) void windowDidBecomeKey(); Q_NOTIFICATION_HANDLER(NSWindowDidResignKeyNotification) void windowDidResignKey(); @@ -212,8 +149,8 @@ public: Q_NOTIFICATION_HANDLER(NSWindowDidEnterFullScreenNotification) void windowDidEnterFullScreen(); Q_NOTIFICATION_HANDLER(NSWindowWillExitFullScreenNotification) void windowWillExitFullScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidExitFullScreenNotification) void windowDidExitFullScreen(); - Q_NOTIFICATION_HANDLER(NSWindowDidOrderOffScreenNotification) void windowDidOrderOffScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidOrderOnScreenAndFinishAnimatingNotification) void windowDidOrderOnScreen(); + Q_NOTIFICATION_HANDLER(NSWindowDidOrderOffScreenNotification) void windowDidOrderOffScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidChangeOcclusionStateNotification) void windowDidChangeOcclusionState(); Q_NOTIFICATION_HANDLER(NSWindowDidChangeScreenNotification) void windowDidChangeScreen(); Q_NOTIFICATION_HANDLER(NSWindowWillCloseNotification) void windowWillClose(); @@ -221,11 +158,8 @@ public: bool windowShouldClose(); bool windowIsPopupType(Qt::WindowType type = Qt::Widget) const; - void reportCurrentWindowState(bool unconditionally = false); - NSInteger windowLevel(Qt::WindowFlags flags); NSUInteger windowStyleMask(Qt::WindowFlags flags); - void setWindowShadow(Qt::WindowFlags flags); void setWindowZoomButton(Qt::WindowFlags flags); #ifndef QT_NO_OPENGL @@ -242,8 +176,6 @@ public: void setMenubar(QCocoaMenuBar *mb); QCocoaMenuBar *menubar() const; - NSCursor *effectiveWindowCursor() const; - void applyEffectiveWindowCursor(); void setWindowCursor(NSCursor *cursor); void registerTouch(bool enable); @@ -252,14 +184,10 @@ public: void setContentBorderAreaEnabled(quintptr identifier, bool enable); void setContentBorderEnabled(bool enable); bool testContentBorderAreaPosition(int position) const; - void applyContentBorderThickness(NSWindow *window); + void applyContentBorderThickness(NSWindow *window = nullptr); void updateNSToolbar(); qreal devicePixelRatio() const Q_DECL_OVERRIDE; - bool isWindowExposable(); - void exposeWindow(); - void obscureWindow(); - void updateExposedGeometry(); QWindow *childWindowAt(QPoint windowPoint); bool shouldRefuseKeyWindowAndFirstResponder(); @@ -271,7 +199,6 @@ public: ParentChanged = 0x1, MissingWindow = 0x2, WindowModalityChanged = 0x4, - ChildNSWindowChanged = 0x8, ContentViewChanged = 0x10, PanelChanged = 0x20, }; @@ -279,20 +206,13 @@ public: Q_FLAG(RecreationReasons) protected: - bool isChildNSWindow() const; - bool isContentView() const; - - void foreachChildNSWindow(void (^block)(QCocoaWindow *)); - void recreateWindowIfNeeded(); - QCocoaNSWindow *createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel); + QCocoaNSWindow *createNSWindow(bool shouldBePanel); QRect nativeWindowGeometry() const; - void reinsertChildWindow(QCocoaWindow *child); - void removeChildWindow(QCocoaWindow *child); Qt::WindowState windowState() const; - void applyWindowState(Qt::WindowState newState); + void applyWindowState(Qt::WindowStates newState); void toggleMaximized(); void toggleFullScreen(); bool isTransitioningToFullScreen() const; @@ -302,24 +222,34 @@ public: // for QNSView friend class QCocoaBackingStore; friend class QCocoaNativeInterface; + bool isContentView() const; + bool alwaysShowToolWindow() const; void removeMonitor(); + enum HandleFlags { + NoHandleFlags = 0, + HandleUnconditionally = 1 + }; + + void handleGeometryChange(); + void handleWindowStateChanged(HandleFlags flags = NoHandleFlags); + void handleExposeEvent(const QRegion ®ion); + NSView *m_view; QCocoaNSWindow *m_nsWindow; - QPointer<QCocoaWindow> m_forwardWindow; // TODO merge to one variable if possible bool m_viewIsEmbedded; // true if the m_view is actually embedded in a "foreign" NSView hiearchy bool m_viewIsToBeEmbedded; // true if the m_view is intended to be embedded in a "foreign" NSView hiearchy Qt::WindowFlags m_windowFlags; - Qt::WindowState m_lastReportedWindowState; + Qt::WindowStates m_lastReportedWindowState; Qt::WindowModality m_windowModality; QPointer<QWindow> m_enterLeaveTargetWindow; bool m_windowUnderMouse; - bool m_inConstructor; + bool m_initialized; bool m_inSetVisible; bool m_inSetGeometry; bool m_inSetStyleMask; @@ -327,18 +257,14 @@ public: // for QNSView QCocoaGLContext *m_glContext; #endif QCocoaMenuBar *m_menubar; - NSCursor *m_windowCursor; + + bool m_needsInvalidateShadow; bool m_hasModalSession; bool m_frameStrutEventsEnabled; - bool m_geometryUpdateExposeAllowed; - bool m_isExposed; - QRect m_exposedGeometry; - qreal m_exposedDevicePixelRatio; + QRect m_exposedRect; int m_registerTouchCount; bool m_resizableTransientParent; - bool m_hiddenByClipping; - bool m_hiddenByAncestor; static const int NoAlertRequest; NSInteger m_alertRequest; @@ -359,10 +285,12 @@ public: // for QNSView }; QHash<quintptr, BorderRange> m_contentBorderAreas; // identifer -> uppper/lower QHash<quintptr, bool> m_enabledContentBorderAreas; // identifer -> enabled state (true/false) - - bool m_hasWindowFilePath; }; +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaWindow *window); +#endif + QT_END_NAMESPACE #endif // QCOCOAWINDOW_H diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 5cd4beb4f0..e86cc2d955 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qcocoawindow.h" #include "qcocoaintegration.h" +#include "qcocoascreen.h" #include "qnswindowdelegate.h" #include "qcocoaeventdispatcher.h" #ifndef QT_NO_OPENGL @@ -46,6 +47,7 @@ #include "qcocoahelpers.h" #include "qcocoanativeinterface.h" #include "qnsview.h" +#include "qnswindow.h" #include <QtCore/qfileinfo.h> #include <QtCore/private/qcore_mac_p.h> #include <qwindow.h> @@ -53,34 +55,22 @@ #include <qpa/qwindowsysteminterface.h> #include <qpa/qplatformscreen.h> #include <QtGui/private/qcoregraphics_p.h> +#include <QtGui/private/qhighdpiscaling_p.h> #include <AppKit/AppKit.h> +#include <QuartzCore/QuartzCore.h> #include <QDebug> #include <vector> +QT_BEGIN_NAMESPACE + enum { defaultWindowWidth = 160, defaultWindowHeight = 160 }; -static bool isMouseEvent(NSEvent *ev) -{ - switch ([ev type]) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: - case NSLeftMouseDragged: - case NSRightMouseDragged: - return true; - default: - return false; - } -} - static void qt_closePopups() { while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { @@ -89,372 +79,7 @@ static void qt_closePopups() } } -@interface NSWindow (FullScreenProperty) -@property(readonly) BOOL qt_fullScreen; -@end - -@implementation NSWindow (FullScreenProperty) - -+ (void)load -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil - usingBlock:^(NSNotification *notification) { - objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); - } - ]; - [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil - usingBlock:^(NSNotification *notification) { - objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - nil, OBJC_ASSOCIATION_RETAIN); - } - ]; -} - -- (BOOL)qt_fullScreen -{ - NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); - return [number boolValue]; -} -@end - -@implementation QNSWindowHelper - -@synthesize window = _window; -@synthesize grabbingMouse = _grabbingMouse; -@synthesize releaseOnMouseUp = _releaseOnMouseUp; - -- (QCocoaWindow *)platformWindow -{ - return _platformWindow.data(); -} - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow -{ - self = [super init]; - if (self) { - _window = window; - _platformWindow = platformWindow; - - _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow]; - - // Prevent Cocoa from releasing the window on close. Qt - // handles the close event asynchronously and we want to - // make sure that m_nsWindow stays valid until the - // QCocoaWindow is deleted by Qt. - [_window setReleasedWhenClosed:NO]; - } - - return self; -} - -- (void)handleWindowEvent:(NSEvent *)theEvent -{ - QCocoaWindow *pw = self.platformWindow; - if (pw && pw->m_forwardWindow) { - if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) { - QNSView *forwardView = qnsview_cast(pw->view()); - if (theEvent.type == NSLeftMouseUp) { - [forwardView mouseUp:theEvent]; - pw->m_forwardWindow.clear(); - } else { - [forwardView mouseDragged:theEvent]; - } - } - if (pw->window()->isTopLevel() && theEvent.type == NSLeftMouseDown) { - pw->m_forwardWindow.clear(); - } - } - - if (theEvent.type == NSLeftMouseDown) { - self.grabbingMouse = YES; - } else if (theEvent.type == NSLeftMouseUp) { - self.grabbingMouse = NO; - if (self.releaseOnMouseUp) { - [self detachFromPlatformWindow]; - [self.window release]; - return; - } - } - - // The call to -[NSWindow sendEvent] may result in the window being deleted - // (e.g., when closing the window by pressing the title bar close button). - [self retain]; - [self.window superSendEvent:theEvent]; - bool windowStillAlive = self.window != nil; // We need to read before releasing - [self release]; - if (!windowStillAlive) - return; - - if (!self.window.delegate) - return; // Already detached, pending NSAppKitDefined event - - if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { - NSPoint loc = [theEvent locationInWindow]; - NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; - NSRect contentFrame = [[self.window contentView] frame]; - if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) - [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; - } -} - -- (void)detachFromPlatformWindow -{ - _platformWindow.clear(); - [self.window.delegate release]; - self.window.delegate = nil; -} - -- (void)clearWindow -{ - if (_window) { - QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - if (cocoaEventDispatcher) { - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); - cocoaEventDispatcherPrivate->removeQueuedUserInputEvents([_window windowNumber]); - } - - _window = nil; - } -} - -- (void)dealloc -{ - _window = nil; - _platformWindow.clear(); - [super dealloc]; -} - -@end - -@implementation QNSWindow - -@synthesize helper = _helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - self = [super initWithContentRect:contentRect - styleMask:windowStyle - backing:NSBackingStoreBuffered - defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is - // set up before the window is shown and needs a proper window) - - if (self) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - // Prevent child NSWindows from becoming the key window in - // order keep the active apperance of the top-level window. - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || !pw->window()->isTopLevel()) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // The default implementation returns NO for title-bar less windows, - // override and return yes here to make sure popup windows such as - // the combobox popup can become the key window. - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - BOOL canBecomeMain = YES; // By default, windows can become the main window - - // Windows with a transient parent (such as combobox popup windows) - // cannot become the main window: - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || !pw->window()->isTopLevel() || pw->window()->transientParent()) - canBecomeMain = NO; - - return canBecomeMain; -} - -- (void) sendEvent: (NSEvent*) theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self close]; - - if (self.helper.grabbingMouse) { - self.helper.releaseOnMouseUp = YES; - } else { - [self.helper detachFromPlatformWindow]; - [self release]; - } -} - -- (void)dealloc -{ - [_helper clearWindow]; - [_helper release]; - _helper = nil; - [super dealloc]; -} - -@end - -@implementation QNSPanel - -@synthesize helper = _helper; - -+ (void)applicationActivationChanged:(NSNotification*)notification -{ - const id sender = self; - NSEnumerator<NSWindow*> *windowEnumerator = nullptr; - NSApplication *application = [NSApplication sharedApplication]; - -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) { - // Unfortunately there's no NSWindowListOrderedBackToFront, - // so we have to manually reverse the order using an array. - NSMutableArray *windows = [[[NSMutableArray alloc] init] autorelease]; - [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack - usingBlock:^(NSWindow *window, BOOL *) { - // For some reason AppKit will give us nil-windows, skip those - if (!window) - return; - - [(NSMutableArray*)windows addObject:window]; - } - ]; - - windowEnumerator = windows.reverseObjectEnumerator; - } else -#endif - { - // No way to get ordered list of windows, so fall back to unordered, - // list, which typically corresponds to window creation order. - windowEnumerator = application.windows.objectEnumerator; - } - - for (NSWindow *window in windowEnumerator) { - // We're meddling with normal and floating windows, so leave others alone - if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) - continue; - - // Windows that hide automatically will keep their NSFloatingWindowLevel, - // and hence be on top of the window stack. We don't want to affect these - // windows, as otherwise we might end up with key windows being ordered - // behind these auto-hidden windows when activating the application by - // clicking on a new tool window. - if (window.hidesOnDeactivate) - continue; - - if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { - QCocoaWindow *cocoaWindow = static_cast<id<QNSWindowProtocol>>(window).helper.platformWindow; - window.level = notification.name == NSApplicationWillResignActiveNotification ? - NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); - } - - // The documentation says that "when a window enters a new level, it’s ordered - // in front of all its peers in that level", but that doesn't seem to be the - // case in practice. To keep the order correct after meddling with the window - // levels, we explicitly order each window to the front. Since we are iterating - // the windows in back-to-front order, this is okey. The call also triggers AppKit - // to re-evaluate the level in relation to windows from other applications, - // working around an issue where our tool windows would stay on top of other - // application windows if activation was transferred to another application by - // clicking on it instead of via the application switcher or Dock. Finally, we - // do this re-ordering for all windows (except auto-hiding ones), otherwise we would - // end up triggering a bug in AppKit where the tool windows would disappear behind - // the application window. - [window orderFront:sender]; - } -} - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - self = [super initWithContentRect:contentRect - styleMask:windowStyle - backing:NSBackingStoreBuffered - defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is - // set up before the window is shown and needs a proper window) - - if (self) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - - if (qpw->alwaysShowToolWindow()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillResignActiveNotification object:nil]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillBecomeActiveNotification object:nil]; - }); - } - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // Only tool or dialog windows should become key: - Qt::WindowType type = pw->window()->type(); - if (type == Qt::Tool || type == Qt::Dialog) - return YES; - - return NO; -} - -- (void) sendEvent: (NSEvent*) theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self.helper detachFromPlatformWindow]; - [self close]; - [self release]; -} - -- (void)dealloc -{ - [_helper clearWindow]; - [_helper release]; - _helper = nil; - [super dealloc]; -} - -@end +Q_LOGGING_CATEGORY(lcCocoaNotifications, "qt.qpa.cocoa.notifications"); static void qRegisterNotificationCallbacks() { @@ -475,38 +100,43 @@ static void qRegisterNotificationCallbacks() [center addObserverForName:notificationName.toNSString() object:nil queue:nil usingBlock:^(NSNotification *notification) { - NSView *view = nullptr; + QVarLengthArray<QCocoaWindow *, 32> cocoaWindows; if ([notification.object isKindOfClass:[NSWindow class]]) { - NSWindow *window = notification.object; - // Only top level NSWindows should notify their QNSViews - if (window.parentWindow) - return; - - if (!window.contentView) - return; - - view = window.contentView; + NSWindow *nsWindow = notification.object; + for (const QWindow *window : QGuiApplication::allWindows()) { + if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle())) + if (cocoaWindow->nativeWindow() == nsWindow) + cocoaWindows += cocoaWindow; + } } else if ([notification.object isKindOfClass:[NSView class]]) { - view = notification.object; + if (QNSView *qnsView = qnsview_cast(notification.object)) + cocoaWindows += qnsView.platformWindow; } else { - qCWarning(lcQpaCocoaWindow) << "Unhandled notifcation" + qCWarning(lcCocoaNotifications) << "Unhandled notifcation" << notification.name << "for" << notification.object; return; } - Q_ASSERT(view); - QCocoaWindow *cocoaWindow = nullptr; - if (QNSView *qnsView = qnsview_cast(view)) - cocoaWindow = qnsView.platformWindow; + if (lcCocoaNotifications().isDebugEnabled()) { + if (cocoaWindows.isEmpty()) { + qCDebug(lcCocoaNotifications) << "Could not find forwarding target for" << + qPrintable(notificationName) << "from" << notification.object; + } else { + QVector<QCocoaWindow *> debugWindows; + for (QCocoaWindow *cocoaWindow : cocoaWindows) + debugWindows += cocoaWindow; + qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) << + "to" << debugWindows; + } + } // FIXME: Could be a foreign window, look up by iterating top level QWindows - if (!cocoaWindow) - return; - - if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { - qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" - << notification.name << "on" << cocoaWindow; + for (QCocoaWindow *cocoaWindow : cocoaWindows) { + if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { + qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + << notification.name << "on" << cocoaWindow; + } } }]; } @@ -515,8 +145,8 @@ Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks) const int QCocoaWindow::NoAlertRequest = -1; -QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) - : QPlatformWindow(tlw) +QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) + : QPlatformWindow(win) , m_view(nil) , m_nsWindow(0) , m_viewIsEmbedded(false) @@ -524,7 +154,7 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) , m_lastReportedWindowState(Qt::WindowNoState) , m_windowModality(Qt::NonModal) , m_windowUnderMouse(false) - , m_inConstructor(true) + , m_initialized(false) , m_inSetVisible(false) , m_inSetGeometry(false) , m_inSetStyleMask(false) @@ -532,42 +162,45 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) , m_glContext(0) #endif , m_menubar(0) - , m_windowCursor(0) + , m_needsInvalidateShadow(false) , m_hasModalSession(false) , m_frameStrutEventsEnabled(false) - , m_geometryUpdateExposeAllowed(false) - , m_isExposed(false) , m_registerTouchCount(0) , m_resizableTransientParent(false) - , m_hiddenByClipping(false) - , m_hiddenByAncestor(false) , m_alertRequest(NoAlertRequest) , monitor(nil) , m_drawContentBorderGradient(false) , m_topContentBorderThickness(0) , m_bottomContentBorderThickness(0) - , m_hasWindowFilePath(false) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::QCocoaWindow" << window(); - QMacAutoReleasePool pool; - if (nativeHandle) { m_view = reinterpret_cast<NSView *>(nativeHandle); [m_view retain]; - } else { + } +} + +void QCocoaWindow::initialize() +{ + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::initialize" << window(); + + QMacAutoReleasePool pool; + + if (!m_view) { m_view = [[QNSView alloc] initWithCocoaWindow:this]; // Enable high-dpi OpenGL for retina displays. Enabling has the side // effect that Cocoa will start calling glViewport(0, 0, width, height), // overriding any glViewport calls in application code. This is usually not a // problem, except if the appilcation wants to have a "custom" viewport. // (like the hellogl example) - if (tlw->supportsOpenGL()) { - BOOL enable = qt_mac_resolveOption(YES, tlw, "_q_mac_wantsBestResolutionOpenGLSurface", + if (window()->supportsOpenGL()) { + BOOL enable = qt_mac_resolveOption(YES, window(), "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); [m_view setWantsBestResolutionOpenGLSurface:enable]; + // See also QCocoaGLContext::makeCurrent for software renderer workarounds. } - BOOL enable = qt_mac_resolveOption(NO, tlw, "_q_mac_wantsLayer", + BOOL enable = qt_mac_resolveOption(NO, window(), "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER"); [m_view setWantsLayer:enable]; } @@ -575,10 +208,11 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) setGeometry(initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight)); recreateWindowIfNeeded(); - tlw->setGeometry(geometry()); - if (tlw->isTopLevel()) - setWindowIcon(tlw->icon()); - m_inConstructor = false; + window()->setGeometry(geometry()); + if (window()->isTopLevel()) + setWindowIcon(window()->icon()); + + m_initialized = true; } QCocoaWindow::~QCocoaWindow() @@ -588,10 +222,7 @@ QCocoaWindow::~QCocoaWindow() QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; [m_nsWindow setContentView:nil]; - [m_nsWindow.helper detachFromPlatformWindow]; - if (m_view.window.parentWindow) - [m_view.window.parentWindow removeChildWindow:m_view.window]; - else if ([m_view superview]) + if ([m_view superview]) [m_view removeFromSuperview]; removeMonitor(); @@ -607,13 +238,8 @@ QCocoaWindow::~QCocoaWindow() QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); } - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - [m_nsWindow removeChildWindow:childWindow->m_nsWindow]; - }); - [m_view release]; [m_nsWindow release]; - [m_windowCursor release]; } QSurfaceFormat QCocoaWindow::format() const @@ -674,134 +300,29 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect; QMacAutoReleasePool pool; + QPlatformWindow::setGeometry(rect); + if (m_viewIsEmbedded) { if (!isForeignWindow()) { [m_view setFrame:NSMakeRect(0, 0, rect.width(), rect.height())]; - } else { - QPlatformWindow::setGeometry(rect); } return; } - if (isChildNSWindow()) { - QPlatformWindow::setGeometry(rect); - NSWindow *parentNSWindow = m_view.window.parentWindow; - NSRect parentWindowFrame = [parentNSWindow contentRectForFrameRect:parentNSWindow.frame]; - clipWindow(parentWindowFrame); - - // call this here: updateGeometry in qnsview.mm is a no-op for this case - QWindowSystemInterface::handleGeometryChange(window(), rect); - QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size())); - } else if (m_nsWindow) { + if (isContentView()) { NSRect bounds = qt_mac_flipRect(rect); - [m_nsWindow setFrame:[m_nsWindow frameRectForContentRect:bounds] display:YES animate:NO]; + [m_view.window setFrame:[m_view.window frameRectForContentRect:bounds] display:YES animate:NO]; } else { [m_view setFrame:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; } - if (isForeignWindow()) - QPlatformWindow::setGeometry(rect); - // will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm) } -void QCocoaWindow::clipChildWindows() -{ - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->clipWindow(m_nsWindow.frame); - }); -} - -void QCocoaWindow::clipWindow(const NSRect &clipRect) -{ - if (!isChildNSWindow()) - return; - - NSRect clippedWindowRect = NSZeroRect; - if (!NSIsEmptyRect(clipRect)) { - NSRect windowFrame = qt_mac_flipRect(QRect(window()->mapToGlobal(QPoint(0, 0)), geometry().size())); - clippedWindowRect = NSIntersectionRect(windowFrame, clipRect); - // Clipping top/left offsets the content. Move it back. - NSPoint contentViewOffset = NSMakePoint(qMax(CGFloat(0), NSMinX(clippedWindowRect) - NSMinX(windowFrame)), - qMax(CGFloat(0), NSMaxY(windowFrame) - NSMaxY(clippedWindowRect))); - [m_view setBoundsOrigin:contentViewOffset]; - } - - if (NSIsEmptyRect(clippedWindowRect)) { - if (!m_hiddenByClipping) { - // We dont call hide() here as we will recurse further down - [m_nsWindow orderOut:nil]; - m_hiddenByClipping = true; - } - } else { - [m_nsWindow setFrame:clippedWindowRect display:YES animate:NO]; - if (m_hiddenByClipping) { - m_hiddenByClipping = false; - if (!m_hiddenByAncestor) { - [m_nsWindow orderFront:nil]; - static_cast<QCocoaWindow *>(QPlatformWindow::parent())->reinsertChildWindow(this); - } - } - } - - // recurse - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->clipWindow(clippedWindowRect); - }); -} - -void QCocoaWindow::hide(bool becauseOfAncestor) -{ - bool visible = [m_nsWindow isVisible]; - - if (!m_hiddenByAncestor && !visible) // Already explicitly hidden - return; - if (m_hiddenByAncestor && becauseOfAncestor) // Trying to hide some child again - return; - - m_hiddenByAncestor = becauseOfAncestor; - - if (!visible) // Could have been clipped before - return; - - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->hide(true); - }); - - [m_nsWindow orderOut:nil]; -} - -void QCocoaWindow::show(bool becauseOfAncestor) -{ - if ([m_nsWindow isVisible]) - return; - - if (m_view.window.parentWindow && !m_view.window.parentWindow.visible) { - m_hiddenByAncestor = true; // Parent still hidden, don't show now - } else if ((becauseOfAncestor == m_hiddenByAncestor) // Was NEITHER explicitly hidden - && !m_hiddenByClipping) { // ... NOR clipped - if (isChildNSWindow()) { - m_hiddenByAncestor = false; - setCocoaGeometry(windowGeometry()); - } - if (!m_hiddenByClipping) { // setCocoaGeometry() can change the clipping status - [m_nsWindow orderFront:nil]; - if (isChildNSWindow()) - static_cast<QCocoaWindow *>(QPlatformWindow::parent())->reinsertChildWindow(this); - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->show(true); - }); - } - } -} - void QCocoaWindow::setVisible(bool visible) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setVisible" << window() << visible; - if (isChildNSWindow() && m_hiddenByClipping) - return; - m_inSetVisible = true; QMacAutoReleasePool pool; @@ -813,6 +334,12 @@ void QCocoaWindow::setVisible(bool visible) // We need to recreate if the modality has changed as the style mask will need updating recreateWindowIfNeeded(); + // We didn't send geometry changes during creation, as that would have confused + // Qt, which expects a show-event to be sent before any resize events. But now + // that the window is made visible, we know that the show-event has been sent + // so we can send the geometry change. FIXME: Get rid of this workaround. + handleGeometryChange(); + // Register popup windows. The Cocoa platform plugin will forward mouse events // to them and close them when needed. if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) @@ -826,34 +353,28 @@ void QCocoaWindow::setVisible(bool visible) if (window()->type() == Qt::Popup) { // QTBUG-30266: a window should not be resizable while a transient popup is open // Since this isn't a native popup, the window manager doesn't close the popup when you click outside - NSUInteger parentStyleMask = [parentCocoaWindow->m_nsWindow styleMask]; + NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); + NSUInteger parentStyleMask = nativeParentWindow.styleMask; if ((m_resizableTransientParent = (parentStyleMask & NSResizableWindowMask)) - && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) - [parentCocoaWindow->m_nsWindow setStyleMask:parentStyleMask & ~NSResizableWindowMask]; + && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) + nativeParentWindow.styleMask &= ~NSResizableWindowMask; } } - // This call is here to handle initial window show correctly: - // - top-level windows need to have backing store content ready when the - // window is shown, sendin the expose event here makes that more likely. - // - QNSViews for child windows are initialy not hidden and won't get the - // viewDidUnhide message. - exposeWindow(); - - if (m_nsWindow) { + if (isContentView()) { QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // setWindowState might have been called while the window was hidden and // will not change the NSWindow state in that case. Sync up here: - applyWindowState(window()->windowState()); + applyWindowState(window()->windowStates()); if (window()->windowState() != Qt::WindowMinimized) { if ((window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet) && parentCocoaWindow) { // show the window as a sheet - [parentCocoaWindow->m_nsWindow beginSheet:m_nsWindow completionHandler:nil]; + [parentCocoaWindow->nativeWindow() beginSheet:m_view.window completionHandler:nil]; } else if (window()->modality() != Qt::NonModal) { // show the window as application modal QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); @@ -861,28 +382,24 @@ void QCocoaWindow::setVisible(bool visible) QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); cocoaEventDispatcherPrivate->beginModalSession(window()); m_hasModalSession = true; - } else if ([m_nsWindow canBecomeKeyWindow]) { + } else if ([m_view.window canBecomeKeyWindow]) { QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); if (cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->cocoaModalSessionStack.isEmpty()) - [m_nsWindow makeKeyAndOrderFront:nil]; + [m_view.window makeKeyAndOrderFront:nil]; else - [m_nsWindow orderFront:nil]; - - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->show(true); - }); + [m_view.window orderFront:nil]; } else { - show(); + [m_view.window orderFront:nil]; } // We want the events to properly reach the popup, dialog, and tool if ((window()->type() == Qt::Popup || window()->type() == Qt::Dialog || window()->type() == Qt::Tool) - && [m_nsWindow isKindOfClass:[NSPanel class]]) { - [(NSPanel *)m_nsWindow setWorksWhenModal:YES]; + && [m_view.window isKindOfClass:[NSPanel class]]) { + ((NSPanel *)m_view.window).worksWhenModal = YES; if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { removeMonitor(); monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask|NSOtherMouseDownMask|NSMouseMovedMask handler:^(NSEvent *e) { @@ -909,20 +426,21 @@ void QCocoaWindow::setVisible(bool visible) QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); - if (m_nsWindow) { + if (isContentView()) { if (m_hasModalSession) { if (cocoaEventDispatcherPrivate) cocoaEventDispatcherPrivate->endModalSession(window()); m_hasModalSession = false; } else { - if ([m_nsWindow isSheet]) { + if ([m_view.window isSheet]) { Q_ASSERT_X(parentCocoaWindow, "QCocoaWindow", "Window modal dialog has no transient parent."); - [parentCocoaWindow->m_nsWindow endSheet:m_nsWindow]; + [parentCocoaWindow->nativeWindow() endSheet:m_view.window]; } } - hide(); - if (m_nsWindow == [NSApp keyWindow] + [m_view.window orderOut:nil]; + + if (m_view.window == [NSApp keyWindow] && !(cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->currentModalSession())) { // Probably because we call runModalSession: outside [NSApp run] in QCocoaEventDispatcher // (e.g., when show()-ing a modal QDialog instead of exec()-ing it), it can happen that @@ -941,10 +459,11 @@ void QCocoaWindow::setVisible(bool visible) QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); if (parentCocoaWindow && window()->type() == Qt::Popup) { + NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); if (m_resizableTransientParent - && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) - // QTBUG-30266: a window should not be resizable while a transient popup is open - [parentCocoaWindow->m_nsWindow setStyleMask:[parentCocoaWindow->m_nsWindow styleMask] | NSResizableWindowMask]; + && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) + // A window should not be resizable while a transient popup is open + nativeParentWindow.styleMask |= NSResizableWindowMask; } } @@ -974,7 +493,7 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) const QWindow * const transientParent = window()->transientParent(); const QCocoaWindow * const transientParentWindow = transientParent ? static_cast<QCocoaWindow *>(transientParent->handle()) : 0; if (transientParentWindow) - windowLevel = qMax([transientParentWindow->m_nsWindow level], windowLevel); + windowLevel = qMax([transientParentWindow->nativeWindow() level], windowLevel); } return windowLevel; @@ -982,70 +501,46 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) { - Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); - NSInteger styleMask = NSBorderlessWindowMask; - if (flags & Qt::FramelessWindowHint) - return styleMask; - if ((type & Qt::Popup) == Qt::Popup) { - if (!windowIsPopupType(type)) { - styleMask = NSUtilityWindowMask | NSResizableWindowMask; - if (!(flags & Qt::CustomizeWindowHint)) { - styleMask |= NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask; - } else { - if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; - if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; - if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; - } - } + const Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); + const bool frameless = (flags & Qt::FramelessWindowHint) || windowIsPopupType(type); + + // Select base window type. + NSUInteger styleMask = frameless ? NSBorderlessWindowMask : NSResizableWindowMask; + + if (frameless) { + // No further customizations for frameless since there are no window decorations. + } else if (flags & Qt::CustomizeWindowHint) { + if (flags & Qt::WindowTitleHint) + styleMask |= NSTitledWindowMask; + if (flags & Qt::WindowCloseButtonHint) + styleMask |= NSClosableWindowMask; + if (flags & Qt::WindowMinimizeButtonHint) + styleMask |= NSMiniaturizableWindowMask; } else { - if (type == Qt::Window && !(flags & Qt::CustomizeWindowHint)) { - styleMask = (NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask); - } else if (type == Qt::Dialog) { - if (flags & Qt::CustomizeWindowHint) { - if (flags & Qt::WindowMaximizeButtonHint) - styleMask = NSResizableWindowMask; - if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; - if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; - if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; - } else { - styleMask = NSResizableWindowMask | NSClosableWindowMask | NSTitledWindowMask; - } - } else { - if (flags & Qt::WindowMaximizeButtonHint) - styleMask |= NSResizableWindowMask; - if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; - if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; - if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; - } + styleMask |= NSClosableWindowMask | NSTitledWindowMask; + + if (type != Qt::Dialog) + styleMask |= NSMiniaturizableWindowMask; } + if (type == Qt::Tool) + styleMask |= NSUtilityWindowMask; + if (m_drawContentBorderGradient) styleMask |= NSTexturedBackgroundWindowMask; // Don't wipe fullscreen state - if (m_nsWindow.styleMask & NSFullScreenWindowMask) + if (m_view.window.styleMask & NSFullScreenWindowMask) styleMask |= NSFullScreenWindowMask; return styleMask; } -void QCocoaWindow::setWindowShadow(Qt::WindowFlags flags) -{ - bool keepShadow = !(flags & Qt::NoDropShadowWindowHint); - [m_nsWindow setHasShadow:(keepShadow ? YES : NO)]; -} - void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) { + if (!isContentView()) + return; + // Disable the zoom (maximize) button for fixed-sized windows and customized // no-WindowMaximizeButtonHint windows. From a Qt perspective it migth be expected // that the button would be removed in the latter case, but disabling it is more @@ -1054,28 +549,27 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) && windowMinimumSize() == windowMaximumSize()); bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint) && !(flags & (Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint))); - [[m_nsWindow standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)]; + [[m_view.window standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)]; } void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) { - if (m_nsWindow && !isChildNSWindow()) { - NSUInteger styleMask = windowStyleMask(flags); - NSInteger level = this->windowLevel(flags); - // While setting style mask we can have -updateGeometry calls on a content + if (isContentView()) { + // While setting style mask we can have handleGeometryChange calls on a content // view with null geometry, reporting an invalid coordinates as a result. m_inSetStyleMask = true; - [m_nsWindow setStyleMask:styleMask]; + m_view.window.styleMask = windowStyleMask(flags); m_inSetStyleMask = false; - [m_nsWindow setLevel:level]; - setWindowShadow(flags); - if (!(flags & Qt::FramelessWindowHint)) { + m_view.window.level = this->windowLevel(flags); + + m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint); + + if (!(flags & Qt::FramelessWindowHint)) setWindowTitle(window()->title()); - } Qt::WindowType type = window()->type(); if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { - NSWindowCollectionBehavior behavior = [m_nsWindow collectionBehavior]; + NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior; if (flags & Qt::WindowFullscreenButtonHint) { behavior |= NSWindowCollectionBehaviorFullScreenPrimary; behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; @@ -1083,28 +577,26 @@ void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; } - [m_nsWindow setCollectionBehavior:behavior]; + m_view.window.collectionBehavior = behavior; } setWindowZoomButton(flags); - } - // Make window ignore mouse events if WindowTransparentForInput is set. - // Note that ignoresMouseEvents has a special initial state where events - // are ignored (passed through) based on window transparency, and that - // setting the property to false does not return us to that state. Instead, - // this makes the window capture all mouse events. Take care to only - // set the property if needed. FIXME: recreate window if needed or find - // some other way to implement WindowTransparentForInput. - if (m_nsWindow) { + // Make window ignore mouse events if WindowTransparentForInput is set. + // Note that ignoresMouseEvents has a special initial state where events + // are ignored (passed through) based on window transparency, and that + // setting the property to false does not return us to that state. Instead, + // this makes the window capture all mouse events. Take care to only + // set the property if needed. FIXME: recreate window if needed or find + // some other way to implement WindowTransparentForInput. bool ignoreMouse = flags & Qt::WindowTransparentForInput; - if (m_nsWindow.ignoresMouseEvents != ignoreMouse) - m_nsWindow.ignoresMouseEvents = ignoreMouse; + if (m_view.window.ignoresMouseEvents != ignoreMouse) + m_view.window.ignoresMouseEvents = ignoreMouse; } m_windowFlags = flags; } -void QCocoaWindow::setWindowState(Qt::WindowState state) +void QCocoaWindow::setWindowState(Qt::WindowStates state) { if (window()->isVisible()) applyWindowState(state); // Window state set for hidden windows take effect when show() is called @@ -1112,45 +604,54 @@ void QCocoaWindow::setWindowState(Qt::WindowState state) void QCocoaWindow::setWindowTitle(const QString &title) { - QMacAutoReleasePool pool; - if (!m_nsWindow) + if (!isContentView()) return; - CFStringRef windowTitle = title.toCFString(); - [m_nsWindow setTitle: const_cast<NSString *>(reinterpret_cast<const NSString *>(windowTitle))]; - CFRelease(windowTitle); + QMacAutoReleasePool pool; + m_view.window.title = title.toNSString(); + + if (title.isEmpty() && !window()->filePath().isEmpty()) { + // Clearing the title should restore the default filename + setWindowFilePath(window()->filePath()); + } } void QCocoaWindow::setWindowFilePath(const QString &filePath) { - QMacAutoReleasePool pool; - if (!m_nsWindow) + if (!isContentView()) return; - QFileInfo fi(filePath); - [m_nsWindow setRepresentedFilename:fi.exists() ? filePath.toNSString() : @""]; - m_hasWindowFilePath = fi.exists(); + QMacAutoReleasePool pool; + + if (window()->title().isNull()) + [m_view.window setTitleWithRepresentedFilename:filePath.toNSString()]; + else + m_view.window.representedFilename = filePath.toNSString(); + + // Changing the file path may affect icon visibility + setWindowIcon(window()->icon()); } void QCocoaWindow::setWindowIcon(const QIcon &icon) { - QMacAutoReleasePool pool; + if (!isContentView()) + return; - NSButton *iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton]; - if (iconButton == nil) { - if (icon.isNull()) - return; - NSString *title = window()->title().toNSString(); - [m_nsWindow setRepresentedURL:[NSURL fileURLWithPath:title]]; - iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton]; + NSButton *iconButton = [m_view.window standardWindowButton:NSWindowDocumentIconButton]; + if (!iconButton) { + // Window icons are only supported on macOS in combination with a document filePath + return; } + + QMacAutoReleasePool pool; + if (icon.isNull()) { - [iconButton setImage:nil]; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + [iconButton setImage:[workspace iconForFile:m_view.window.representedFilename]]; } else { QPixmap pixmap = icon.pixmap(QSize(22, 22)); NSImage *image = static_cast<NSImage *>(qt_mac_create_nsimage(pixmap)); - [iconButton setImage:image]; - [image release]; + [iconButton setImage:[image autorelease]]; } } @@ -1174,34 +675,22 @@ void QCocoaWindow::raise() qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::raise" << window(); // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) - if (!m_nsWindow) + if (!isContentView()) return; - if (isChildNSWindow()) { - if (m_hiddenByClipping) - return; - } - if ([m_nsWindow isVisible]) { - if (isChildNSWindow()) { - // -[NSWindow orderFront:] doesn't work with attached windows. - // The only solution is to remove and add the child window. - // This will place it on top of all the other NSWindows. - NSWindow *parentNSWindow = m_view.window.parentWindow; - [parentNSWindow removeChildWindow:m_nsWindow]; - [parentNSWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; - } else { - { - // Clean up autoreleased temp objects from orderFront immediately. - // Failure to do so has been observed to cause leaks also beyond any outer - // autorelease pool (for example around a complete QWindow - // construct-show-raise-hide-delete cyle), counter to expected autoreleasepool - // behavior. - QMacAutoReleasePool pool; - [m_nsWindow orderFront: m_nsWindow]; - } - static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); - if (raiseProcess) { - [NSApp activateIgnoringOtherApps:YES]; - } + + if (m_view.window.visible) { + { + // Clean up autoreleased temp objects from orderFront immediately. + // Failure to do so has been observed to cause leaks also beyond any outer + // autorelease pool (for example around a complete QWindow + // construct-show-raise-hide-delete cyle), counter to expected autoreleasepool + // behavior. + QMacAutoReleasePool pool; + [m_view.window orderFront:m_view.window]; + } + static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); + if (raiseProcess) { + [NSApp activateIgnoringOtherApps:YES]; } } } @@ -1209,34 +698,16 @@ void QCocoaWindow::raise() void QCocoaWindow::lower() { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::lower" << window(); - if (!m_nsWindow) + if (!isContentView()) return; - if (isChildNSWindow()) { - if (m_hiddenByClipping) - return; - } - if ([m_nsWindow isVisible]) { - if (isChildNSWindow()) { - // -[NSWindow orderBack:] doesn't work with attached windows. - // The only solution is to remove and add all the child windows except this one. - // This will keep the current window at the bottom while adding the others on top of it, - // hopefully in the same order (this is not documented anywhere in the Cocoa documentation). - NSWindow *parentNSWindow = m_view.window.parentWindow; - NSArray *children = [parentNSWindow.childWindows copy]; - for (NSWindow *child in children) - if (m_nsWindow != child) { - [parentNSWindow removeChildWindow:child]; - [parentNSWindow addChildWindow:child ordered:NSWindowAbove]; - } - } else { - [m_nsWindow orderBack: m_nsWindow]; - } - } + + if (m_view.window.visible) + [m_view.window orderBack:m_view.window]; } bool QCocoaWindow::isExposed() const { - return m_isExposed; + return !m_exposedRect.isEmpty(); } bool QCocoaWindow::isOpaque() const @@ -1247,7 +718,7 @@ bool QCocoaWindow::isOpaque() const bool translucent = window()->format().alphaBufferSize() > 0 || window()->opacity() < 1 - || [qnsview_cast(m_view) hasMask] + || !window()->mask().isEmpty() || (surface()->supportsOpenGL() && openglSourfaceOrder == -1); return !translucent; } @@ -1255,24 +726,24 @@ bool QCocoaWindow::isOpaque() const void QCocoaWindow::propagateSizeHints() { QMacAutoReleasePool pool; - if (!m_nsWindow) + if (!isContentView()) return; - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::propagateSizeHints" << window() << "\n" - << " min/max" << windowMinimumSize() << windowMaximumSize() - << "size increment" << windowSizeIncrement() - << " basesize" << windowBaseSize() - << " geometry" << windowGeometry(); + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::propagateSizeHints" << window() + << "min:" << windowMinimumSize() << "max:" << windowMaximumSize() + << "increment:" << windowSizeIncrement() + << "base:" << windowBaseSize(); + + const NSWindow *window = m_view.window; // Set the minimum content size. - const QSize minimumSize = windowMinimumSize(); + QSize minimumSize = windowMinimumSize(); if (!minimumSize.isValid()) // minimumSize is (-1, -1) when not set. Make that (0, 0) for Cocoa. - [m_nsWindow setContentMinSize : NSMakeSize(0.0, 0.0)]; - [m_nsWindow setContentMinSize : NSMakeSize(minimumSize.width(), minimumSize.height())]; + minimumSize = QSize(0, 0); + window.contentMinSize = NSSizeFromCGSize(minimumSize.toCGSize()); // Set the maximum content size. - const QSize maximumSize = windowMaximumSize(); - [m_nsWindow setContentMaxSize : NSMakeSize(maximumSize.width(), maximumSize.height())]; + window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize()); // The window may end up with a fixed size; in this case the zoom button should be disabled. setWindowZoomButton(m_windowFlags); @@ -1282,42 +753,65 @@ void QCocoaWindow::propagateSizeHints() QSize sizeIncrement = windowSizeIncrement(); if (sizeIncrement.isEmpty()) sizeIncrement = QSize(1, 1); - [m_nsWindow setResizeIncrements:NSSizeFromCGSize(sizeIncrement.toCGSize())]; + window.resizeIncrements = NSSizeFromCGSize(sizeIncrement.toCGSize()); QRect rect = geometry(); QSize baseSize = windowBaseSize(); - if (!baseSize.isNull() && baseSize.isValid()) { - [m_nsWindow setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES]; - } + if (!baseSize.isNull() && baseSize.isValid()) + [window setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES]; } void QCocoaWindow::setOpacity(qreal level) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setOpacity" << level; - if (m_nsWindow) { - [m_nsWindow setAlphaValue:level]; - [m_nsWindow setOpaque: isOpaque()]; - } + if (!isContentView()) + return; + + m_view.window.alphaValue = level; } void QCocoaWindow::setMask(const QRegion ®ion) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMask" << window() << region; - if (m_nsWindow) - [m_nsWindow setBackgroundColor:[NSColor clearColor]]; - [qnsview_cast(m_view) setMaskRegion:®ion]; - [m_nsWindow setOpaque:isOpaque()]; + if (m_view.layer) { + if (!region.isEmpty()) { + QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable(); + for (const QRect &r : region) + CGPathAddRect(maskPath, nullptr, r.toCGRect()); + CAShapeLayer *maskLayer = [CAShapeLayer layer]; + maskLayer.path = maskPath; + m_view.layer.mask = maskLayer; + } else { + m_view.layer.mask = nil; + } + } + + if (isContentView()) { + // Setting the mask requires invalidating the NSWindow shadow, but that needs + // to happen after the backingstore has been redrawn, so that AppKit can pick + // up the new window shape based on the backingstore content. Doing a display + // directly here is not an option, as the window might not be exposed at this + // time, and so would not result in an updated backingstore. + m_needsInvalidateShadow = true; + [m_view setNeedsDisplay:YES]; + + // FIXME: [NSWindow invalidateShadow] has no effect when in layer-backed mode, + // so if the mask is changed after the initial mask is applied, it will not + // result in any visual change to the shadow. This is an Apple bug, and there + // may be ways to work around it, such as calling setFrame on the window to + // trigger some internal invalidation, but that needs more research. + } } bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab; - if (!m_nsWindow) + if (!isContentView()) return false; - if (grab && ![m_nsWindow isKeyWindow]) - [m_nsWindow makeKeyWindow]; + if (grab && ![m_view.window isKeyWindow]) + [m_view.window makeKeyWindow]; return true; } @@ -1325,11 +819,11 @@ bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) bool QCocoaWindow::setMouseGrabEnabled(bool grab) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab; - if (!m_nsWindow) + if (!isContentView()) return false; - if (grab && ![m_nsWindow isKeyWindow]) - [m_nsWindow makeKeyWindow]; + if (grab && ![m_view.window isKeyWindow]) + [m_view.window makeKeyWindow]; return true; } @@ -1358,7 +852,7 @@ NSView *QCocoaWindow::view() const NSWindow *QCocoaWindow::nativeWindow() const { - return m_nsWindow; + return m_view.window; } void QCocoaWindow::setEmbeddedInForeignView(bool embedded) @@ -1369,8 +863,32 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) m_nsWindow = 0; } +// ----------------------- NSView notifications ----------------------- + +void QCocoaWindow::viewDidChangeFrame() +{ + handleGeometryChange(); +} + +/*! + Callback for NSViewGlobalFrameDidChangeNotification. + + Posted whenever an NSView object that has attached surfaces (that is, + NSOpenGLContext objects) moves to a different screen, or other cases + where the NSOpenGLContext object needs to be updated. +*/ +void QCocoaWindow::viewDidChangeGlobalFrame() +{ + [m_view setNeedsDisplay:YES]; +} + // ----------------------- NSWindow notifications ----------------------- +// Note: The following notifications are delivered to every QCocoaWindow +// that is a child of the NSWindow that triggered the notification. Each +// callback should make sure to filter out notifications if they do not +// apply to that QCocoaWindow, e.g. if the window is not a content view. + void QCocoaWindow::windowWillMove() { // Close any open popups on window move @@ -1379,54 +897,39 @@ void QCocoaWindow::windowWillMove() void QCocoaWindow::windowDidMove() { - if (isChildNSWindow()) + if (!isContentView()) return; - [qnsview_cast(m_view) updateGeometry]; + handleGeometryChange(); // Moving a window might bring it out of maximized state - reportCurrentWindowState(); + handleWindowStateChanged(); } void QCocoaWindow::windowDidResize() { - if (!m_nsWindow) - return; - - if (isChildNSWindow()) + if (!isContentView()) return; - clipChildWindows(); - [qnsview_cast(m_view) updateGeometry]; + handleGeometryChange(); if (!m_view.inLiveResize) - reportCurrentWindowState(); -} - -void QCocoaWindow::viewDidChangeFrame() -{ - [qnsview_cast(m_view) updateGeometry]; -} - -/*! - Callback for NSViewGlobalFrameDidChangeNotification. - - Posted whenever an NSView object that has attached surfaces (that is, - NSOpenGLContext objects) moves to a different screen, or other cases - where the NSOpenGLContext object needs to be updated. -*/ -void QCocoaWindow::viewDidChangeGlobalFrame() -{ - updateExposedGeometry(); + handleWindowStateChanged(); } void QCocoaWindow::windowDidEndLiveResize() { - reportCurrentWindowState(); + if (!isContentView()) + return; + + handleWindowStateChanged(); } void QCocoaWindow::windowDidBecomeKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -1443,6 +946,9 @@ void QCocoaWindow::windowDidBecomeKey() void QCocoaWindow::windowDidResignKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -1459,43 +965,61 @@ void QCocoaWindow::windowDidResignKey() void QCocoaWindow::windowDidMiniaturize() { - reportCurrentWindowState(); + if (!isContentView()) + return; + + handleWindowStateChanged(); } void QCocoaWindow::windowDidDeminiaturize() { - reportCurrentWindowState(); + if (!isContentView()) + return; + + handleWindowStateChanged(); } void QCocoaWindow::windowWillEnterFullScreen() { + if (!isContentView()) + return; + // The NSWindow needs to be resizable, otherwise we'll end up with // the normal window geometry, centered in the middle of the screen // on a black background. The styleMask will be reset below. - m_nsWindow.styleMask |= NSResizableWindowMask; + m_view.window.styleMask |= NSResizableWindowMask; } void QCocoaWindow::windowDidEnterFullScreen() { - Q_ASSERT_X(m_nsWindow.qt_fullScreen, "QCocoaWindow", + if (!isContentView()) + return; + + Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); // Reset to original styleMask setWindowFlags(m_windowFlags); - reportCurrentWindowState(); + handleWindowStateChanged(); } void QCocoaWindow::windowWillExitFullScreen() { + if (!isContentView()) + return; + // The NSWindow needs to be resizable, otherwise we'll end up with // a weird zoom animation. The styleMask will be reset below. - m_nsWindow.styleMask |= NSResizableWindowMask; + m_view.window.styleMask |= NSResizableWindowMask; } void QCocoaWindow::windowDidExitFullScreen() { - Q_ASSERT_X(!m_nsWindow.qt_fullScreen, "QCocoaWindow", + if (!isContentView()) + return; + + Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); // Reset to original styleMask @@ -1504,7 +1028,7 @@ void QCocoaWindow::windowDidExitFullScreen() Qt::WindowState requestedState = window()->windowState(); // Deliver update of QWindow state - reportCurrentWindowState(); + handleWindowStateChanged(); if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) { // We were only going out of full screen as an intermediate step before @@ -1513,30 +1037,22 @@ void QCocoaWindow::windowDidExitFullScreen() } } -void QCocoaWindow::windowDidOrderOffScreen() +void QCocoaWindow::windowDidOrderOnScreen() { - obscureWindow(); + [m_view setNeedsDisplay:YES]; } -void QCocoaWindow::windowDidOrderOnScreen() +void QCocoaWindow::windowDidOrderOffScreen() { - exposeWindow(); + handleExposeEvent(QRegion()); } void QCocoaWindow::windowDidChangeOcclusionState() { - // Several unit tests expect paint and/or expose events for windows that are - // sometimes (unpredictably) occluded and some unit tests depend on QWindow::isExposed. - // Don't send Expose/Obscure events when running under QTestLib. - static const bool onTestLib = qt_mac_resolveOption(false, "QT_QTESTLIB_RUNNING"); - if (!onTestLib) { - if ((NSUInteger)[m_view.window occlusionState] & NSWindowOcclusionStateVisible) { - exposeWindow(); - } else { - // Send Obscure events on window occlusion to stop animations. - obscureWindow(); - } - } + if (m_view.window.occlusionState & NSWindowOcclusionStateVisible) + [m_view setNeedsDisplay:YES]; + else + handleExposeEvent(QRegion()); } void QCocoaWindow::windowDidChangeScreen() @@ -1546,8 +1062,6 @@ void QCocoaWindow::windowDidChangeScreen() if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) QWindowSystemInterface::handleWindowScreenChanged(window(), cocoaScreen->screen()); - - updateExposedGeometry(); } void QCocoaWindow::windowWillClose() @@ -1572,6 +1086,111 @@ bool QCocoaWindow::windowShouldClose() return accepted; } +// ----------------------------- QPA forwarding ----------------------------- + +void QCocoaWindow::handleGeometryChange() +{ + // Prevent geometry change during initialization, as that will result + // in a resize event, and Qt expects those to come after the show event. + // FIXME: Remove once we've clarified the Qt behavior for this. + if (!m_initialized) + return; + + // Don't send the geometry change if the QWindow is designated to be + // embedded in a foreign view hierarchy but has not actually been + // embedded yet - it's too early. + if (m_viewIsToBeEmbedded && !m_viewIsEmbedded) + return; + + // It can happen that the current NSWindow is nil (if we are changing styleMask + // from/to borderless, and the content view is being re-parented), which results + // in invalid coordinates. + if (m_inSetStyleMask && !m_view.window) + return; + + const bool isEmbedded = m_viewIsToBeEmbedded || m_viewIsEmbedded; + + QRect newGeometry; + if (isContentView() && !isEmbedded) { + // Content views are positioned at (0, 0) in the window, so we resolve via the window + CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame]; + + // The result above is in native screen coordinates, so remap to the Qt coordinate system + newGeometry = QCocoaScreen::primaryScreen()->mapFromNative(QRectF::fromCGRect(contentRect)).toRect(); + } else { + // QNSView has isFlipped set, so no need to remap the geometry + newGeometry = QRectF::fromCGRect(m_view.frame).toRect(); + } + + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleGeometryChange" << window() + << "current" << geometry() << "new" << newGeometry; + + QWindowSystemInterface::handleGeometryChange(window(), newGeometry); + + // Guard against processing window system events during QWindow::setGeometry + // calls, which Qt and Qt applications do not expect. + if (!m_inSetGeometry) + QWindowSystemInterface::flushWindowSystemEvents(); +} + +void QCocoaWindow::handleExposeEvent(const QRegion ®ion) +{ + const QRect previouslyExposedRect = m_exposedRect; + + // Ideally we'd implement isExposed() in terms of these properties, + // plus the occlusionState of the NSWindow, and let the expose event + // pull the exposed state out when needed. However, when the window + // is first shown we receive a drawRect call where the occlusionState + // of the window is still hidden, but we still want to prepare the + // window for display by issuing an expose event to Qt. To work around + // this we don't use the occlusionState directly, but instead base + // the exposed state on the region we get in, which in the case of + // a window being obscured is an empty region, and in the case of + // a drawRect call is a non-null region, even if occlusionState + // is still hidden. This ensures the window is prepared for display. + if (m_view.window.visible && m_view.window.screen + && !geometry().size().isEmpty() && !region.isEmpty() + && !m_view.hiddenOrHasHiddenAncestor) { + m_exposedRect = region.boundingRect(); + } else { + m_exposedRect = QRect(); + } + + QWindowPrivate *windowPrivate = qt_window_private(window()); + if (windowPrivate->updateRequestPending) { + // We can only deliver update request events when the window is exposed, + // and we also have to make sure we deliver any change to the exposed + // rect as a real expose event (including going from non-exposed to + // exposed). FIXME: Should this logic live in QGuiApplication? + if (isExposed() && m_exposedRect == previouslyExposedRect) { + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "as update request"; + windowPrivate->deliverUpdateRequest(); + return; + } else { + // Since updateRequestPending is still set, we will issue a deferred setNeedsDisplay + // from drawRect and get back into this code on the next display cycle, delivering + // the pending update request. + } + } + + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); + QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region); +} + +void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) +{ + Qt::WindowState currentState = windowState(); + if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) + return; + + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleWindowStateChanged" << + m_lastReportedWindowState << "-->" << currentState; + + QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( + window(), currentState, m_lastReportedWindowState); + m_lastReportedWindowState = currentState; +} + // -------------------------------------------------------------------------- bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const @@ -1597,26 +1216,13 @@ QCocoaGLContext *QCocoaWindow::currentContext() const #endif /*! - Checks if the window is a non-top level QWindow with a NSWindow. - - \sa _q_platform_MacUseNSWindow, QT_MAC_USE_NSWINDOW -*/ -bool QCocoaWindow::isChildNSWindow() const -{ - return m_view.window.parentWindow != nil; -} - -/*! Checks if the window is the content view of its immediate NSWindow. Being the content view of a NSWindow means the QWindow is the highest accessible NSView object in the window's view hierarchy. - This can only happen in two cases, either if the QWindow is - itself a top level window, or if it's a child NSWindow. - - \sa isChildNSWindow + This is the case if the QWindow is a top level window. */ bool QCocoaWindow::isContentView() const { @@ -1624,33 +1230,16 @@ bool QCocoaWindow::isContentView() const } /*! - Iterates child NSWindows that have a corresponding QCocoaWindow. -*/ -void QCocoaWindow::foreachChildNSWindow(void (^block)(QCocoaWindow *)) -{ - NSArray *windows = m_view.window.childWindows; - [windows enumerateObjectsUsingBlock:^(NSWindow *window, NSUInteger index, BOOL *stop) { - Q_UNUSED(index); - Q_UNUSED(stop); - if (QNSView *view = qnsview_cast(window.contentView)) - block(view.platformWindow); - }]; -} - -/*! Recreates (or removes) the NSWindow for this QWindow, if needed. - A QWindow may need a corresponding NSWindow, depending on whether - or not it's a top level or not (or explicitly set to be a child - NSWindow), whether it is a NSPanel or not, etc. + A QWindow may need a corresponding NSWindow/NSPanel, depending on + whether or not it's a top level or not, window flags, etc. */ void QCocoaWindow::recreateWindowIfNeeded() { QMacAutoReleasePool pool; QPlatformWindow *parentWindow = QPlatformWindow::parent(); - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() - << "parent" << (parentWindow ? parentWindow->window() : 0); RecreationReasons recreateReason = RecreationNotNeeded; @@ -1668,79 +1257,56 @@ void QCocoaWindow::recreateWindowIfNeeded() if (m_windowModality != window()->modality()) recreateReason |= WindowModalityChanged; - const bool shouldBeChildNSWindow = parentWindow && qt_mac_resolveOption(NO, - window(), "_q_platform_MacUseNSWindow", "QT_MAC_USE_NSWINDOW"); - - if (isChildNSWindow() != shouldBeChildNSWindow) - recreateReason |= ChildNSWindowChanged; - - const bool shouldBeContentView = (!parentWindow && !m_viewIsEmbedded) || shouldBeChildNSWindow; + const bool shouldBeContentView = !parentWindow && !(m_viewIsToBeEmbedded || m_viewIsEmbedded); if (isContentView() != shouldBeContentView) recreateReason |= ContentViewChanged; Qt::WindowType type = window()->type(); const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]]; - const bool shouldBePanel = shouldBeContentView && !shouldBeChildNSWindow && + const bool shouldBePanel = shouldBeContentView && ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); if (isPanel != shouldBePanel) recreateReason |= PanelChanged; - if (recreateReason == RecreationNotNeeded) { - qCDebug(lcQpaCocoaWindow) << "No need to recreate NSWindow"; - return; - } + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason; - qCDebug(lcQpaCocoaWindow) << "Reconfiguring NSWindow due to" << recreateReason; + if (recreateReason == RecreationNotNeeded) + return; QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow); - if (shouldBeChildNSWindow) { - QWindow *parentQWindow = parentWindow->window(); - // Ensure that all parents in the hierarchy are also child NSWindows - if (!parentQWindow->property("_q_platform_MacUseNSWindow").toBool()) { - parentQWindow->setProperty("_q_platform_MacUseNSWindow", QVariant(true)); - parentCocoaWindow->recreateWindowIfNeeded(); - } - } - // Remove current window (if any) if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) { - qCDebug(lcQpaCocoaWindow) << "Getting rid of existing window" << m_nsWindow; - [m_nsWindow closeAndRelease]; - if (isChildNSWindow()) - [m_view.window.parentWindow removeChildWindow:m_view.window]; - if (isContentView()) { - // We explicitly disassociate m_view from the window's contentView, - // as AppKit does not automatically do this in response to removing - // the view from the NSThemeFrame subview list, so we might end up - // with a NSWindow contentView pointing to a deallocated NSView. - m_view.window.contentView = nil; + if (m_nsWindow) { + qCDebug(lcQpaCocoaWindow) << "Getting rid of existing window" << m_nsWindow; + [m_nsWindow closeAndRelease]; + if (isContentView()) { + // We explicitly disassociate m_view from the window's contentView, + // as AppKit does not automatically do this in response to removing + // the view from the NSThemeFrame subview list, so we might end up + // with a NSWindow contentView pointing to a deallocated NSView. + m_view.window.contentView = nil; + } + m_nsWindow = 0; } - m_nsWindow = 0; } if (shouldBeContentView) { bool noPreviousWindow = m_nsWindow == 0; + QCocoaNSWindow *newWindow = nullptr; if (noPreviousWindow) - m_nsWindow = createNSWindow(shouldBeChildNSWindow, shouldBePanel); - - if (m_view.window.parentWindow) { - if (!shouldBeChildNSWindow || (recreateReason & ParentChanged)) - [m_view.window.parentWindow removeChildWindow:m_view.window]; - m_forwardWindow = oldParentCocoaWindow; - } + newWindow = createNSWindow(shouldBePanel); // Move view to new NSWindow if needed - if (m_nsWindow.contentView != m_view) { - qCDebug(lcQpaCocoaWindow) << "Ensuring that view is content view for" << m_nsWindow; + if (newWindow) { + qCDebug(lcQpaCocoaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow; [m_view setPostsFrameChangedNotifications:NO]; - [m_view retain]; - if (m_view.superview) // m_view comes from another NSWindow - [m_view removeFromSuperview]; - [m_nsWindow setContentView:m_view]; - [m_view release]; + [newWindow setContentView:m_view]; [m_view setPostsFrameChangedNotifications:YES]; + + m_nsWindow = newWindow; + Q_ASSERT(m_view.window == m_nsWindow); } } @@ -1751,21 +1317,10 @@ void QCocoaWindow::recreateWindowIfNeeded() propagateSizeHints(); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); + setWindowFilePath(window()->filePath()); setWindowState(window()->windowState()); - } else if (shouldBeChildNSWindow) { - if (!m_hiddenByClipping) { - [parentCocoaWindow->m_nsWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; - parentCocoaWindow->reinsertChildWindow(this); - } - - // Set properties after the window has been made a child NSWindow - setCocoaGeometry(windowGeometry()); - setWindowFlags(window()->flags()); } else { // Child windows have no NSWindow, link the NSViews instead. - if ([m_view superview]) - [m_view removeFromSuperview]; - [parentCocoaWindow->m_view addSubview:m_view]; QRect rect = windowGeometry(); // Prevent setting a (0,0) window size; causes opengl context @@ -1781,29 +1336,18 @@ void QCocoaWindow::recreateWindowIfNeeded() if (!qFuzzyCompare(opacity, qreal(1.0))) setOpacity(opacity); + setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window())); + // top-level QWindows may have an attached NSToolBar, call // update function which will attach to the NSWindow. if (!parentWindow) updateNSToolbar(); } -void QCocoaWindow::reinsertChildWindow(QCocoaWindow *child) +void QCocoaWindow::requestUpdate() { - const QObjectList &childWindows = window()->children(); - int childIndex = childWindows.indexOf(child->window()); - Q_ASSERT(childIndex != -1); - - for (int i = childIndex; i < childWindows.size(); ++i) { - QWindow *window = static_cast<QWindow *>(childWindows.at(i)); - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - if (!cocoaWindow) - continue; - - NSWindow *nsChild = cocoaWindow->m_nsWindow; - if (i != childIndex) - [m_nsWindow removeChildWindow:nsChild]; - [m_nsWindow addChildWindow:nsChild ordered:NSWindowAbove]; - } + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::requestUpdate" << window(); + [m_view setNeedsDisplay:YES]; } void QCocoaWindow::requestActivateWindow() @@ -1813,10 +1357,8 @@ void QCocoaWindow::requestActivateWindow() [window makeKeyWindow]; } -QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel) +QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) { - qCDebug(lcQpaCocoaWindow) << "createNSWindow" << shouldBeChildNSWindow << shouldBePanel; - QMacAutoReleasePool pool; QRect rect = geometry(); @@ -1848,44 +1390,58 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool sh // Create NSWindow Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class]; - NSUInteger styleMask = shouldBeChildNSWindow ? NSBorderlessWindowMask : windowStyleMask(flags); - QCocoaNSWindow *window = [[windowClass alloc] initWithContentRect:frame - screen:cocoaScreen->nativeScreen() styleMask:styleMask qPlatformWindow:this]; - - window.restorable = NO; - window.level = shouldBeChildNSWindow ? NSNormalWindowLevel : windowLevel(flags); - - if (!isOpaque()) { - window.backgroundColor = [NSColor clearColor]; - window.opaque = NO; + QCocoaNSWindow *nsWindow = [[windowClass alloc] initWithContentRect:frame + styleMask:windowStyleMask(flags) + // Deferring window creation breaks OpenGL (the GL context is + // set up before the window is shown and needs a proper window) + backing:NSBackingStoreBuffered defer:NO + screen:cocoaScreen->nativeScreen()]; + + Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow", + "Resulting NSScreen should match the requested NSScreen"); + + nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; + + // Prevent Cocoa from releasing the window on close. Qt + // handles the close event asynchronously and we want to + // make sure that NSWindow stays valid until the + // QCocoaWindow is deleted by Qt. + [nsWindow setReleasedWhenClosed:NO]; + + if (alwaysShowToolWindow()) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillResignActiveNotification object:nil]; + [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillBecomeActiveNotification object:nil]; + }); } - Q_ASSERT(!(shouldBePanel && shouldBeChildNSWindow)); + if (targetScreen != window()->screen()) + QWindowSystemInterface::handleWindowScreenChanged(window(), targetScreen); + + nsWindow.restorable = NO; + nsWindow.level = windowLevel(flags); if (shouldBePanel) { // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set - window.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow(); + nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow(); // Make popup windows show on the same desktop as the parent full-screen window - window.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary; + nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary; if ((type & Qt::Popup) == Qt::Popup) { - window.hasShadow = YES; - window.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; + nsWindow.hasShadow = YES; + nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; } - } else if (shouldBeChildNSWindow) { - window.collectionBehavior = - NSWindowCollectionBehaviorManaged - | NSWindowCollectionBehaviorIgnoresCycle - | NSWindowCollectionBehaviorFullScreenAuxiliary; - window.hasShadow = NO; - window.animationBehavior = NSWindowAnimationBehaviorNone; } // Persist modality so we can detect changes later on m_windowModality = QPlatformWindow::window()->modality(); - applyContentBorderThickness(window); + applyContentBorderThickness(nsWindow); // Prevent CoreGraphics RGB32 -> RGB64 backing store conversions on deep color // displays by forcing 8-bit components, unless a deep color format has been @@ -1898,11 +1454,11 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool sh surfaceFormat.blueBufferSize() > 8; bool usesLayer = view().layer; if (usesCoreGraphics && !usesDeepColor && !usesLayer) { - [window setDynamicDepthLimit:NO]; - [window setDepthLimit:NSWindowDepthTwentyfourBitRGB]; + [nsWindow setDynamicDepthLimit:NO]; + [nsWindow setDepthLimit:NSWindowDepthTwentyfourBitRGB]; } - return window; + return nsWindow; } bool QCocoaWindow::alwaysShowToolWindow() const @@ -1921,10 +1477,10 @@ void QCocoaWindow::removeMonitor() // Returns the current global screen geometry for the nswindow associated with this window. QRect QCocoaWindow::nativeWindowGeometry() const { - if (!m_nsWindow || isChildNSWindow()) + if (!isContentView()) return geometry(); - NSRect rect = [m_nsWindow frame]; + NSRect rect = m_view.window.frame; QPlatformScreen *onScreen = QPlatformScreen::platformScreenForWindow(window()); int flippedY = onScreen->geometry().height() - rect.origin.y - rect.size.height; // account for nswindow inverted y. QRect qRect = QRect(rect.origin.x, flippedY, rect.size.width, rect.size.height); @@ -1938,13 +1494,15 @@ QRect QCocoaWindow::nativeWindowGeometry() const updated yet, so window()->windowState() will reflect the previous state that was reported to QtGui. */ -void QCocoaWindow::applyWindowState(Qt::WindowState newState) +void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) { - const Qt::WindowState currentState = windowState(); - if (newState == currentState) + if (!isContentView()) return; - if (!m_nsWindow) + const Qt::WindowState currentState = windowState(); + const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); + + if (newState == currentState) return; const NSSize contentSize = m_view.frame.size; @@ -1952,23 +1510,25 @@ void QCocoaWindow::applyWindowState(Qt::WindowState newState) // If content view width or height is 0 then the window animations will crash so // do nothing. We report the current state back to reflect the failed operation. qWarning("invalid window content view size, check your window geometry"); - reportCurrentWindowState(true); + handleWindowStateChanged(HandleUnconditionally); return; } - if (m_nsWindow.styleMask & NSUtilityWindowMask) { - // Utility panels cannot be fullscreen - qWarning() << window()->type() << "windows can not be made full screen"; - reportCurrentWindowState(true); + const NSWindow *nsWindow = m_view.window; + + if (nsWindow.styleMask & NSUtilityWindowMask + && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { + qWarning() << window()->type() << "windows can not be made" << newState; + handleWindowStateChanged(HandleUnconditionally); return; } - const id sender = m_nsWindow; + const id sender = nsWindow; // First we need to exit states that can't transition directly to other states switch (currentState) { case Qt::WindowMinimized: - [m_nsWindow deminiaturize:sender]; + [nsWindow deminiaturize:sender]; Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", "[NSWindow deminiaturize:] is synchronous"); break; @@ -1994,7 +1554,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowState newState) toggleMaximized(); break; case Qt::WindowMinimized: - [m_nsWindow miniaturize:sender]; + [nsWindow miniaturize:sender]; break; case Qt::WindowNoState: if (windowState() == Qt::WindowMaximized) @@ -2007,27 +1567,31 @@ void QCocoaWindow::applyWindowState(Qt::WindowState newState) void QCocoaWindow::toggleMaximized() { + const NSWindow *window = m_view.window; + // The NSWindow needs to be resizable, otherwise the window will // not be possible to zoom back to non-zoomed state. - const bool wasResizable = m_nsWindow.styleMask & NSResizableWindowMask; - m_nsWindow.styleMask |= NSResizableWindowMask; + const bool wasResizable = window.styleMask & NSResizableWindowMask; + window.styleMask |= NSResizableWindowMask; - const id sender = m_nsWindow; - [m_nsWindow zoom:sender]; + const id sender = window; + [window zoom:sender]; if (!wasResizable) - m_nsWindow.styleMask &= ~NSResizableWindowMask; + window.styleMask &= ~NSResizableWindowMask; } void QCocoaWindow::toggleFullScreen() { + const NSWindow *window = m_view.window; + // The window needs to have the correct collection behavior for the // toggleFullScreen call to have an effect. The collection behavior // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen. - m_nsWindow.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; + window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; - const id sender = m_nsWindow; - [m_nsWindow toggleFullScreen:sender]; + const id sender = window; + [window toggleFullScreen:sender]; } bool QCocoaWindow::isTransitioningToFullScreen() const @@ -2055,22 +1619,12 @@ Qt::WindowState QCocoaWindow::windowState() const return Qt::WindowNoState; } -void QCocoaWindow::reportCurrentWindowState(bool unconditionally) -{ - Qt::WindowState currentState = windowState(); - if (!unconditionally && currentState == m_lastReportedWindowState) - return; - - QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( - window(), currentState, m_lastReportedWindowState); - m_lastReportedWindowState = currentState; -} - bool QCocoaWindow::setWindowModified(bool modified) { - if (!m_nsWindow) + if (!isContentView()) return false; - [m_nsWindow setDocumentEdited:(modified?YES:NO)]; + + m_view.window.documentEdited = modified; return true; } @@ -2084,51 +1638,19 @@ QCocoaMenuBar *QCocoaWindow::menubar() const return m_menubar; } -// Finds the effective cursor for this window by walking up the -// ancestor chain (including this window) until a set cursor is -// found. Returns nil if there is not set cursor. -NSCursor *QCocoaWindow::effectiveWindowCursor() const -{ - - if (m_windowCursor) - return m_windowCursor; - if (!QPlatformWindow::parent()) - return nil; - return static_cast<QCocoaWindow *>(QPlatformWindow::parent())->effectiveWindowCursor(); -} - -// Applies the cursor as returned by effectiveWindowCursor(), handles -// the special no-cursor-set case by setting the arrow cursor. -void QCocoaWindow::applyEffectiveWindowCursor() -{ - NSCursor *effectiveCursor = effectiveWindowCursor(); - if (effectiveCursor) { - [effectiveCursor set]; - } else { - // We wold like to _unset_ the cursor here; but there is no such - // API. Fall back to setting the default arrow cursor. - [[NSCursor arrowCursor] set]; - } -} - void QCocoaWindow::setWindowCursor(NSCursor *cursor) { - if (m_windowCursor == cursor) + // Setting a cursor in a foreign view is not supported + if (isForeignWindow()) return; - // Setting a cursor in a foregin view is not supported. - if (isForeignWindow()) + QNSView *view = qnsview_cast(m_view); + if (cursor == view.cursor) return; - [m_windowCursor release]; - m_windowCursor = cursor; - [m_windowCursor retain]; + view.cursor = cursor; - // The installed view tracking area (see QNSView updateTrackingAreas) will - // handle cursor updates on mouse enter/leave. Handle the case where the - // mouse is on the this window by changing the cursor immediately. - if (m_windowUnderMouse) - applyEffectiveWindowCursor(); + [m_view.window invalidateCursorRectsForView:m_view]; } void QCocoaWindow::registerTouch(bool enable) @@ -2147,35 +1669,38 @@ void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickne bool enable = (topThickness > 0 || bottomThickness > 0); m_drawContentBorderGradient = enable; - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower) { m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower)); - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::setContentBorderAreaEnabled(quintptr identifier, bool enable) { m_enabledContentBorderAreas.insert(identifier, enable); - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::setContentBorderEnabled(bool enable) { m_drawContentBorderGradient = enable; - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::applyContentBorderThickness(NSWindow *window) { + if (!window && isContentView()) + window = m_view.window; + if (!window) return; if (!m_drawContentBorderGradient) { - [window setStyleMask:[window styleMask] & ~NSTexturedBackgroundWindowMask]; - [[[window contentView] superview] setNeedsDisplay:YES]; + window.styleMask = window.styleMask & ~NSTexturedBackgroundWindowMask; + [window.contentView.superview setNeedsDisplay:YES]; window.titlebarAppearsTransparent = NO; return; } @@ -2214,22 +1739,23 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) void QCocoaWindow::updateNSToolbar() { - if (!m_nsWindow) + if (!isContentView()) return; NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window()); + const NSWindow *window = m_view.window; - if ([m_nsWindow toolbar] == toolbar) + if (window.toolbar == toolbar) return; - [m_nsWindow setToolbar: toolbar]; - [m_nsWindow setShowsToolbarButton:YES]; + window.toolbar = toolbar; + window.showsToolbarButton = YES; } bool QCocoaWindow::testContentBorderAreaPosition(int position) const { - return m_nsWindow && m_drawContentBorderGradient && - 0 <= position && position < [m_nsWindow contentBorderThicknessForEdge: NSMaxYEdge]; + return isContentView() && m_drawContentBorderGradient && + 0 <= position && position < [m_view.window contentBorderThicknessForEdge:NSMaxYEdge]; } qreal QCocoaWindow::devicePixelRatio() const @@ -2243,85 +1769,6 @@ qreal QCocoaWindow::devicePixelRatio() const return backingSize.height; } -// Returns whether the window can be expose, which it can -// if it is on screen and has a valid geometry. -bool QCocoaWindow::isWindowExposable() -{ - QSize size = geometry().size(); - bool validGeometry = (size.width() > 0 && size.height() > 0); - bool validScreen = ([[m_view window] screen] != 0); - bool nonHiddenSuperView = ![[m_view superview] isHidden]; - return (validGeometry && validScreen && nonHiddenSuperView); -} - -// Exposes the window by posting an expose event to QWindowSystemInterface -void QCocoaWindow::exposeWindow() -{ - m_geometryUpdateExposeAllowed = true; - - if (!isWindowExposable()) - return; - - if (window()->isTopLevel()) { - // Update the QWindow's screen property. This property is set - // to QGuiApplication::primaryScreen() at QWindow construciton - // time, and we won't get a NSWindowDidChangeScreenNotification - // on show. The case where the window is initially displayed - // on a non-primary screen needs special handling here. - if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_nsWindow.screen)) - window()->setScreen(cocoaScreen->screen()); - } - - if (!m_isExposed) { - m_isExposed = true; - m_exposedGeometry = geometry(); - m_exposedDevicePixelRatio = devicePixelRatio(); - QRect geometry(QPoint(0, 0), m_exposedGeometry.size()); - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow: exposeWindow" << window() << geometry; - QWindowSystemInterface::handleExposeEvent(window(), geometry); - } -} - -// Obscures the window by posting an empty expose event to QWindowSystemInterface -void QCocoaWindow::obscureWindow() -{ - if (m_isExposed) { - m_geometryUpdateExposeAllowed = false; - m_isExposed = false; - - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::obscureWindow" << window(); - QWindowSystemInterface::handleExposeEvent(window(), QRegion()); - } -} - -// Updates window geometry by posting an expose event to QWindowSystemInterface -void QCocoaWindow::updateExposedGeometry() -{ - // updateExposedGeometry is not allowed to send the initial expose. If you want - // that call exposeWindow(); - if (!m_geometryUpdateExposeAllowed) - return; - - // Do not send incorrect exposes in case the window is not even visible yet. - // We might get here as a result of a resize() from QWidget's show(), for instance. - if (!window()->isVisible()) - return; - - if (!isWindowExposable()) - return; - - if (m_exposedGeometry.size() == geometry().size() && m_exposedDevicePixelRatio == devicePixelRatio()) - return; - - m_isExposed = true; - m_exposedGeometry = geometry(); - m_exposedDevicePixelRatio = devicePixelRatio(); - - QRect geometry(QPoint(0, 0), m_exposedGeometry.size()); - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::updateExposedGeometry" << window() << geometry; - QWindowSystemInterface::handleExposeEvent(window(), geometry); -} - QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint) { QWindow *targetWindow = window(); @@ -2371,8 +1818,11 @@ QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const QMargins QCocoaWindow::frameMargins() const { - NSRect frameW = [m_nsWindow frame]; - NSRect frameC = [m_nsWindow contentRectForFrameRect:frameW]; + if (!isContentView()) + return QMargins(); + + NSRect frameW = m_view.window.frame; + NSRect frameC = [m_view.window contentRectForFrameRect:frameW]; return QMargins(frameW.origin.x - frameC.origin.x, (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height), @@ -2385,4 +1835,19 @@ void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled) m_frameStrutEventsEnabled = enabled; } +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaWindow *window) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "QCocoaWindow(" << (const void *)window; + if (window) + debug << ", window=" << window->window(); + debug << ')'; + return debug; +} +#endif // !QT_NO_DEBUG_STREAM + #include "moc_qcocoawindow.cpp" + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac.mm b/src/plugins/platforms/cocoa/qmultitouch_mac.mm index f0ea3b1e66..79f8af7783 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm +++ b/src/plugins/platforms/cocoa/qmultitouch_mac.mm @@ -39,10 +39,14 @@ #include "qmultitouch_mac_p.h" #include "qcocoahelpers.h" +#include <private/qtouchdevice_p.h> QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcInputDevices, "qt.qpa.input.devices") + QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches; +QHash<quint64, QTouchDevice*> QCocoaTouch::_touchDevices; QPointF QCocoaTouch::_screenReferencePos; QPointF QCocoaTouch::_trackpadReferencePos; int QCocoaTouch::_idAssignmentCount = 0; @@ -209,4 +213,19 @@ QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) return touchPoints.values(); } +QTouchDevice *QCocoaTouch::getTouchDevice(QTouchDevice::DeviceType type, quint64 id) +{ + QTouchDevice *ret = _touchDevices.value(id); + if (!ret) { + ret = new QTouchDevice; + ret->setType(type); + ret->setCapabilities(QTouchDevice::Position | QTouchDevice::NormalizedPosition | QTouchDevice::MouseEmulation); + QWindowSystemInterface::registerTouchDevice(ret); + _touchDevices.insert(id, ret); + qCDebug(lcInputDevices) << "touch device" << id << "of type" << type + << "registered as Qt device" << QTouchDevicePrivate::get(ret)->id; + } + return ret; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h index 77af86c9c7..044bcd1882 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h +++ b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h @@ -66,8 +66,10 @@ class QCocoaTouch public: static QList<QWindowSystemInterface::TouchPoint> getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch); static void setMouseInDraggingState(bool inDraggingState); + static QTouchDevice *getTouchDevice(QTouchDevice::DeviceType type, quint64 id); private: + static QHash<quint64, QTouchDevice*> _touchDevices; static QHash<qint64, QCocoaTouch*> _currentTouches; static QPointF _screenReferencePos; static QPointF _trackpadReferencePos; diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 384f14ba3a..f8903725a6 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -51,19 +51,12 @@ QT_BEGIN_NAMESPACE class QCocoaWindow; -class QCocoaBackingStore; class QCocoaGLContext; QT_END_NAMESPACE Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); @interface QT_MANGLE_NAMESPACE(QNSView) : NSView <NSTextInputClient> { - QCocoaBackingStore* m_backingStore; - QPoint m_backingStoreOffset; - QRegion m_maskRegion; - CGImageRef m_maskImage; - uchar *m_maskData; - bool m_shouldInvalidateWindowShadow; QPointer<QCocoaWindow> m_platformWindow; NSTrackingArea *m_trackingArea; Qt::MouseButtons m_buttons; @@ -73,6 +66,7 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); QPointer<QObject> m_composingFocusObject; bool m_sendKeyEvent; QStringList *currentCustomDragTypes; + bool m_dontOverrideCtrlLMB; bool m_sendUpAsRightButton; Qt::KeyboardModifiers currentWheelModifiers; #ifndef QT_NO_OPENGL @@ -89,28 +83,22 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); QSet<quint32> m_acceptedKeyDowns; } +@property (nonatomic, retain) NSCursor *cursor; + - (id)init; - (id)initWithCocoaWindow:(QCocoaWindow *)platformWindow; #ifndef QT_NO_OPENGL - (void)setQCocoaGLContext:(QCocoaGLContext *)context; #endif -- (void)flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset; -- (void)clearBackingStore:(QCocoaBackingStore *)backingStore; -- (void)setMaskRegion:(const QRegion *)region; -- (void)invalidateWindowShadowIfNeeded; - (void)drawRect:(NSRect)dirtyRect; -- (void)drawBackingStoreUsingCoreGraphics:(NSRect)dirtyRect; -- (void)updateGeometry; - (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification; - (void)viewDidHide; -- (void)viewDidUnhide; - (void)removeFromSuperview; - (void)cancelComposingText; - (BOOL)isFlipped; - (BOOL)acceptsFirstResponder; - (BOOL)becomeFirstResponder; -- (BOOL)hasMask; - (BOOL)isOpaque; - (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 2e64204fb7..c888551729 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -53,6 +53,7 @@ #include <QtCore/qsysinfo.h> #include <private/qguiapplication_p.h> #include <private/qcoregraphics_p.h> +#include <private/qwindow_p.h> #include "qcocoabackingstore.h" #ifndef QT_NO_OPENGL #include "qcocoaglcontext.h" @@ -69,16 +70,6 @@ Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") #endif Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") -static QTouchDevice *touchDevice = 0; - -static bool _q_dontOverrideCtrlLMB = false; - -@interface NSEvent (Qt_Compile_Leopard_DeviceDelta) - - (CGFloat)deviceDeltaX; - - (CGFloat)deviceDeltaY; - - (CGFloat)deviceDeltaZ; -@end - @interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject { QNSView *view; @@ -121,7 +112,7 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)cursorUpdate:(NSEvent *)theEvent { - [self cursorUpdate:theEvent]; + [view cursorUpdate:theEvent]; } @end @@ -133,17 +124,9 @@ static bool _q_dontOverrideCtrlLMB = false; @implementation QT_MANGLE_NAMESPACE(QNSView) -+ (void)initialize -{ - _q_dontOverrideCtrlLMB = qt_mac_resolveOption(false, "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); -} - - (id) init { if (self = [super initWithFrame:NSZeroRect]) { - m_backingStore = 0; - m_maskImage = 0; - m_shouldInvalidateWindowShadow = false; m_buttons = Qt::NoButton; m_acceptedMouseDowns = Qt::NoButton; m_frameStrutButtons = Qt::NoButton; @@ -153,6 +136,7 @@ static bool _q_dontOverrideCtrlLMB = false; m_shouldSetGLContextinDrawRect = false; #endif currentCustomDragTypes = 0; + m_dontOverrideCtrlLMB = false; m_sendUpAsRightButton = false; m_inputSource = 0; m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self]; @@ -160,28 +144,19 @@ static bool _q_dontOverrideCtrlLMB = false; m_scrolling = false; m_updatingDrag = false; m_currentlyInterpretedKeyEvent = 0; - - if (!touchDevice) { - touchDevice = new QTouchDevice; - touchDevice->setType(QTouchDevice::TouchPad); - touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::NormalizedPosition | QTouchDevice::MouseEmulation); - QWindowSystemInterface::registerTouchDevice(touchDevice); - } - m_isMenuView = false; self.focusRingType = NSFocusRingTypeNone; + self.cursor = nil; } return self; } - (void)dealloc { - CGImageRelease(m_maskImage); if (m_trackingArea) { [self removeTrackingArea:m_trackingArea]; [m_trackingArea release]; } - m_maskImage = 0; [m_inputSource release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [m_mouseMoveHelper release]; @@ -199,6 +174,7 @@ static bool _q_dontOverrideCtrlLMB = false; m_platformWindow = platformWindow; m_sendKeyEvent = false; + m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, platformWindow->window(), "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); m_trackingArea = nil; #ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR @@ -226,6 +202,22 @@ static bool _q_dontOverrideCtrlLMB = false; return self; } +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithString:[super description]]; + +#ifndef QT_NO_DEBUG_STREAM + QString platformWindowDescription; + QDebug debug(&platformWindowDescription); + debug.nospace() << "; " << m_platformWindow << ">"; + + NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1]; + [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()]; +#endif + + return description; +} + #ifndef QT_NO_OPENGL - (void) setQCocoaGLContext:(QCocoaGLContext *)context { @@ -249,18 +241,13 @@ static bool _q_dontOverrideCtrlLMB = false; if ([self superview]) { m_platformWindow->m_viewIsEmbedded = true; QWindowSystemInterface::handleGeometryChange(m_platformWindow->window(), m_platformWindow->geometry()); - m_platformWindow->updateExposedGeometry(); + [self setNeedsDisplay:YES]; QWindowSystemInterface::flushWindowSystemEvents(); } else { m_platformWindow->m_viewIsEmbedded = false; } } -- (void)viewDidMoveToWindow -{ - m_backingStore = Q_NULLPTR; -} - - (QWindow *)topLevelWindow { if (!m_platformWindow) @@ -279,77 +266,6 @@ static bool _q_dontOverrideCtrlLMB = false; return focusWindow; } -- (void)updateGeometry -{ - if (!m_platformWindow) - return; - - QRect geometry; - - if (self.window.parentWindow) { - return; -#if 0 - //geometry = QRectF::fromCGRect([self frame]).toRect(); - qDebug() << "nsview updateGeometry" << m_platformWindow->window(); - QRect screenRect = QRectF::fromCGRect([m_platformWindow->m_nsWindow convertRectToScreen:[self frame]]).toRect(); - qDebug() << "screenRect" << screenRect; - - screenRect.moveTop(qt_mac_flipYCoordinate(screenRect.y() + screenRect.height())); - geometry = QRect(m_platformWindow->window()->parent()->mapFromGlobal(screenRect.topLeft()), screenRect.size()); - qDebug() << "geometry" << geometry; -#endif - //geometry = QRect(screenRect.origin.x, qt_mac_flipYCoordinate(screenRect.origin.y + screenRect.size.height), screenRect.size.width, screenRect.size.height); - } else if (m_platformWindow->m_nsWindow) { - // top level window, get window rect and flip y. - NSRect rect = [self frame]; - NSRect windowRect = [[self window] frame]; - geometry = QRect(windowRect.origin.x, qt_mac_flipYCoordinate(windowRect.origin.y + rect.size.height), rect.size.width, rect.size.height); - } else if (m_platformWindow->m_viewIsToBeEmbedded) { - // embedded child window, use the frame rect ### merge with case below - geometry = QRectF::fromCGRect(NSRectToCGRect([self bounds])).toRect(); - } else { - // child window, use the frame rect - geometry = QRectF::fromCGRect(NSRectToCGRect([self frame])).toRect(); - } - - if (m_platformWindow->m_nsWindow && geometry == m_platformWindow->geometry()) - return; - - const bool isResize = geometry.size() != m_platformWindow->geometry().size(); - - // It can happen that self.window is nil (if we are changing - // styleMask from/to borderless and content view is being re-parented) - // - this results in an invalid coordinates. - if (m_platformWindow->m_inSetStyleMask && !self.window) - return; - - qCDebug(lcQpaCocoaWindow) << "[QNSView udpateGeometry:]" << m_platformWindow->window() - << "current" << m_platformWindow->geometry() << "new" << geometry; - - // Call setGeometry on QPlatformWindow. (not on QCocoaWindow, - // doing that will initiate a geometry change it and possibly create - // an infinite loop when this notification is triggered again.) - m_platformWindow->QPlatformWindow::setGeometry(geometry); - - // Don't send the geometry change if the QWindow is designated to be - // embedded in a foreign view hiearchy but has not actually been - // embedded yet - it's too early. - if (m_platformWindow->m_viewIsToBeEmbedded && !m_platformWindow->m_viewIsEmbedded) - return; - - // Send a geometry change event to Qt, if it's ready to handle events - if (!m_platformWindow->m_inConstructor) { - QWindowSystemInterface::handleGeometryChange(m_platformWindow->window(), geometry); - m_platformWindow->updateExposedGeometry(); - // Guard against processing window system events during QWindow::setGeometry - // calles, which Qt and Qt applications do not excpect. - if (!m_platformWindow->m_inSetGeometry) - QWindowSystemInterface::flushWindowSystemEvents(); - else if (isResize) - m_backingStore = 0; - } -} - - (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification { Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) @@ -361,12 +277,13 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)viewDidHide { - m_platformWindow->obscureWindow(); -} + if (!m_platformWindow->isExposed()) + return; -- (void)viewDidUnhide -{ - m_platformWindow->exposeWindow(); + m_platformWindow->handleExposeEvent(QRegion()); + + // Note: setNeedsDisplay is automatically called for + // viewDidUnhide so no reason to override it here. } - (void)removeFromSuperview @@ -375,35 +292,6 @@ static bool _q_dontOverrideCtrlLMB = false; [super removeFromSuperview]; } -- (void) flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset -{ - qCDebug(lcQpaCocoaWindow) << "[QNSView flushBackingStore:]" << m_platformWindow->window() << region.rectCount() << region.boundingRect() << offset; - - m_backingStore = backingStore; - m_backingStoreOffset = offset * m_backingStore->paintDevice()->devicePixelRatio(); - - // Prevent buildup of NSDisplayCycle objects during setNeedsDisplayInRect, which - // would normally be released as part of the root runloop's autorelease pool, but - // can be kept alive during repeated painting which starve the root runloop. - // FIXME: Move this to the event dispatcher, to cover more cases of starvation. - // FIXME: Figure out if there's a way to detect and/or prevent runloop starvation. - QMacAutoReleasePool pool; - - for (const QRect &rect : region) - [self setNeedsDisplayInRect:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; -} - -- (void)clearBackingStore:(QCocoaBackingStore *)backingStore -{ - if (backingStore == m_backingStore) - m_backingStore = 0; -} - -- (BOOL) hasMask -{ - return !m_maskRegion.isEmpty(); -} - - (BOOL) isOpaque { if (!m_platformWindow) @@ -411,49 +299,21 @@ static bool _q_dontOverrideCtrlLMB = false; return m_platformWindow->isOpaque(); } -- (void) setMaskRegion:(const QRegion *)region -{ - m_shouldInvalidateWindowShadow = true; - m_maskRegion = *region; - if (m_maskImage) - CGImageRelease(m_maskImage); - if (region->isEmpty()) { - m_maskImage = 0; - return; - } - - const QRect &rect = region->boundingRect(); - QImage tmp(rect.size(), QImage::Format_RGB32); - tmp.fill(Qt::white); - QPainter p(&tmp); - p.setClipRegion(*region); - p.fillRect(rect, Qt::black); - p.end(); - QImage maskImage = QImage(rect.size(), QImage::Format_Indexed8); - for (int y=0; y<rect.height(); ++y) { - const uint *src = (const uint *) tmp.constScanLine(y); - uchar *dst = maskImage.scanLine(y); - for (int x=0; x<rect.width(); ++x) { - dst[x] = src[x] & 0xff; - } - } - m_maskImage = qt_mac_toCGImageMask(maskImage); -} - -- (void)invalidateWindowShadowIfNeeded -{ - if (m_shouldInvalidateWindowShadow && m_platformWindow->m_nsWindow) { - [m_platformWindow->m_nsWindow invalidateShadow]; - m_shouldInvalidateWindowShadow = false; - } -} - - (void)drawRect:(NSRect)dirtyRect { + Q_UNUSED(dirtyRect); + if (!m_platformWindow) return; - qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:]" << m_platformWindow->window() << QRectF::fromCGRect(NSRectToCGRect(dirtyRect)); + QRegion exposedRegion; + const NSRect *dirtyRects; + NSInteger numDirtyRects; + [self getRectsBeingDrawn:&dirtyRects count:&numDirtyRects]; + for (int i = 0; i < numDirtyRects; ++i) + exposedRegion += QRectF::fromCGRect(dirtyRects[i]).toRect(); + + qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion; #ifndef QT_NO_OPENGL if (m_glContext && m_shouldSetGLContextinDrawRect) { @@ -462,85 +322,44 @@ static bool _q_dontOverrideCtrlLMB = false; } #endif - if (m_platformWindow->m_drawContentBorderGradient) - NSDrawWindowBackground(dirtyRect); - - if (m_backingStore) - [self drawBackingStoreUsingCoreGraphics:dirtyRect]; + m_platformWindow->handleExposeEvent(exposedRegion); + + if (qt_window_private(m_platformWindow->window())->updateRequestPending) { + // A call to QWindow::requestUpdate was issued during the expose event, or we + // had to deliver a real expose event and still need to deliver the update. + // But AppKit will reset the needsDisplay state of the view after completing + // the current display cycle, so we need to defer the request to redisplay. + // FIXME: Perhaps this should be a trigger to enable CADisplayLink? + qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:] issuing deferred setNeedsDisplay due to pending update request"; + dispatch_async(dispatch_get_main_queue (), ^{ + [self setNeedsDisplay:YES]; + }); + } +} - [self invalidateWindowShadowIfNeeded]; +- (BOOL)wantsUpdateLayer +{ + return YES; } -// Draws the backing store content to the QNSView using Core Graphics. -// This function assumes that the QNSView is in a configuration that -// supports Core Graphics, such as "classic" mode or layer mode with -// the default layer. -- (void)drawBackingStoreUsingCoreGraphics:(NSRect)dirtyRect +- (void)updateLayer { - if (!m_backingStore) + if (!m_platformWindow) return; - // Calculate source and target rects. The target rect is the dirtyRect: - CGRect dirtyWindowRect = NSRectToCGRect(dirtyRect); - - // The backing store source rect will be larger on retina displays. - // Scale dirtyRect by the device pixel ratio: - const qreal devicePixelRatio = m_backingStore->paintDevice()->devicePixelRatio(); - CGRect dirtyBackingRect = CGRectMake(dirtyRect.origin.x * devicePixelRatio, - dirtyRect.origin.y * devicePixelRatio, - dirtyRect.size.width * devicePixelRatio, - dirtyRect.size.height * devicePixelRatio); - - NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext]; - CGContextRef cgContext = (CGContextRef) [nsGraphicsContext graphicsPort]; - - // Translate coordiate system from CoreGraphics (bottom-left) to NSView (top-left): - CGContextSaveGState(cgContext); - int dy = dirtyWindowRect.origin.y + CGRectGetMaxY(dirtyWindowRect); - - CGContextTranslateCTM(cgContext, 0, dy); - CGContextScaleCTM(cgContext, 1, -1); - - // If a mask is set, modify the sub image accordingly: - CGImageRef subMask = 0; - if (m_maskImage) { - subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyWindowRect); - CGContextClipToMask(cgContext, dirtyWindowRect, subMask); - } - - // Clip out and draw the correct sub image from the (shared) backingstore: - CGRect backingStoreRect = CGRectMake( - dirtyBackingRect.origin.x + m_backingStoreOffset.x(), - dirtyBackingRect.origin.y + m_backingStoreOffset.y(), - dirtyBackingRect.size.width, - dirtyBackingRect.size.height - ); - CGImageRef bsCGImage = qt_mac_toCGImage(m_backingStore->toImage()); - - // Prevent potentially costly color conversion by assiging the display - // color space to the backingstore image. - CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(bsCGImage, - self.window.screen.colorSpace.CGColorSpace); - - CGImageRef cleanImg = CGImageCreateWithImageInRect(displayColorSpaceImage, backingStoreRect); + qCDebug(lcQpaCocoaWindow) << "[QNSView updateLayer]" << m_platformWindow->window(); - // Optimization: Copy frame buffer content instead of blending for - // top-level windows where Qt fills the entire window content area. - // (But don't overpaint the title-bar gradient) - if (m_platformWindow->m_nsWindow && !m_platformWindow->m_drawContentBorderGradient) - CGContextSetBlendMode(cgContext, kCGBlendModeCopy); - - CGContextDrawImage(cgContext, dirtyWindowRect, cleanImg); + // FIXME: Find out if there's a way to resolve the dirty rect like in drawRect: + m_platformWindow->handleExposeEvent(QRectF::fromCGRect(self.bounds).toRect()); +} - // Clean-up: - CGContextRestoreGState(cgContext); - CGImageRelease(cleanImg); - CGImageRelease(subMask); - CGImageRelease(bsCGImage); - CGImageRelease(displayColorSpaceImage); +- (void)viewDidChangeBackingProperties +{ + if (self.layer) + self.layer.contentsScale = self.window.backingScaleFactor; } -- (BOOL) isFlipped +- (BOOL)isFlipped { return YES; } @@ -661,12 +480,6 @@ static bool _q_dontOverrideCtrlLMB = false; QPointF qtWindowPoint; QPointF qtScreenPoint; QNSView *targetView = self; - if (m_platformWindow && m_platformWindow->m_forwardWindow) { - if (theEvent.type == NSLeftMouseDragged || theEvent.type == NSLeftMouseUp) - targetView = qnsview_cast(m_platformWindow->m_forwardWindow->view()); - else - m_platformWindow->m_forwardWindow.clear(); - } if (!targetView.platformWindow) return; @@ -760,7 +573,8 @@ static bool _q_dontOverrideCtrlLMB = false; Q_UNUSED(qtScreenPoint); // Maintain masked state for the button for use by MouseDragged and MouseUp. - const bool masked = [self hasMask] && !m_maskRegion.contains(qtWindowPoint.toPoint()); + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); if (masked) m_acceptedMouseDowns &= ~button; else @@ -858,8 +672,8 @@ static bool _q_dontOverrideCtrlLMB = false; [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; Q_UNUSED(qtScreenPoint); - const bool masked = [self hasMask] && !m_maskRegion.contains(qtWindowPoint.toPoint()); - + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); // Maintain masked state for the button for use by MouseDragged and Up. if (masked) m_acceptedMouseDowns &= ~Qt::LeftButton; @@ -875,7 +689,7 @@ static bool _q_dontOverrideCtrlLMB = false; if ([self hasMarkedText]) { [[NSTextInputContext currentInputContext] handleEvent:theEvent]; } else { - if (!_q_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { + if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { m_buttons |= Qt::RightButton; m_sendUpAsRightButton = true; } else { @@ -972,8 +786,16 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)cursorUpdate:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - m_platformWindow->applyEffectiveWindowCursor(); + qCDebug(lcQpaCocoaWindow) << "[QNSView cursorUpdate:]" << self.cursor; + + // Note: We do not get this callback when moving from a subview that + // uses the legacy cursorRect API, so the cursor is reset to the arrow + // cursor. See rdar://34183708 + + if (self.cursor) + [self.cursor set]; + else + [super cursorUpdate:theEvent]; } - (void)mouseMovedImpl:(NSEvent *)theEvent @@ -994,7 +816,7 @@ static bool _q_dontOverrideCtrlLMB = false; // the time of the leave. This is dificult to accomplish by // handling mouseEnter and mouseLeave envents, since they are sent // individually to different views. - if (m_platformWindow->m_nsWindow && childWindow) { + if (m_platformWindow->isContentView() && childWindow) { if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); m_platformWindow->m_enterLeaveTargetWindow = childWindow; @@ -1021,7 +843,7 @@ static bool _q_dontOverrideCtrlLMB = false; return; // Top-level windows generate enter events for sub-windows. - if (!m_platformWindow->m_nsWindow) + if (!m_platformWindow->isContentView()) return; QPointF windowPoint; @@ -1043,7 +865,7 @@ static bool _q_dontOverrideCtrlLMB = false; return; // Top-level windows generate leave events for sub-windows. - if (!m_platformWindow->m_nsWindow) + if (!m_platformWindow->isContentView()) return; QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); @@ -1240,8 +1062,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } - (void)touchesMovedWithEvent:(NSEvent *)event @@ -1251,8 +1073,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } - (void)touchesEndedWithEvent:(NSEvent *)event @@ -1262,8 +1084,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } - (void)touchesCancelledWithEvent:(NSEvent *)event @@ -1273,8 +1095,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } #ifndef QT_NO_GESTURES @@ -1304,12 +1126,12 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if ([self handleGestureAsBeginEnd:event]) return; - qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification]; + qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification] << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::ZoomNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::ZoomNativeGesture, [event magnification], windowPoint, screenPoint); } @@ -1319,12 +1141,12 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) return; static bool zoomIn = true; - qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn; + qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::SmartZoomNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SmartZoomNativeGesture, zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); zoomIn = !zoomIn; } @@ -1341,7 +1163,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::RotateNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::RotateNativeGesture, -[event rotation], windowPoint, screenPoint); } @@ -1350,7 +1172,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if (!m_platformWindow) return; - qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY]; + qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY] << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; @@ -1366,7 +1188,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) else if ([event deltaY] == -1) angle = 270.0f; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::SwipeNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SwipeNativeGesture, angle, windowPoint, screenPoint); } @@ -1379,8 +1201,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), timestamp, Qt::BeginNativeGesture, + qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::BeginNativeGesture, windowPoint, screenPoint); } @@ -1389,12 +1211,12 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if (!m_platformWindow) return; - qCDebug(lcQpaGestures) << "endGestureWithEvent"; + qCDebug(lcQpaGestures) << "endGestureWithEvent" << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), timestamp, Qt::EndNativeGesture, + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::EndNativeGesture, windowPoint, screenPoint); } #endif // QT_NO_GESTURES @@ -2126,7 +1948,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); if (nativeDrag->currentDrag()) { // The drag was started from within the application - response = QWindowSystemInterface::handleDrag(target, nativeDrag->platformDropData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + response = QWindowSystemInterface::handleDrag(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); [self updateCursorFromDragResponse:response drag:nativeDrag]; } else { QCocoaDropData mimeData([sender draggingPasteboard]); @@ -2170,7 +1992,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); if (nativeDrag->currentDrag()) { // The drag was started from within the application - response = QWindowSystemInterface::handleDrop(target, nativeDrag->platformDropData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); } else { QCocoaDropData mimeData([sender draggingPasteboard]); response = QWindowSystemInterface::handleDrop(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h new file mode 100644 index 0000000000..1258fddb31 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QNSWINDOW_H +#define QNSWINDOW_H + +#include <qglobal.h> +#include <QPointer> +#include "qt_mac_p.h" + +#include <AppKit/AppKit.h> + +QT_FORWARD_DECLARE_CLASS(QCocoaWindow) + +@interface NSWindow (FullScreenProperty) +@property(readonly) BOOL qt_fullScreen; +@end + +@protocol QNSWindowProtocol +@optional +- (BOOL)canBecomeKeyWindow; +- (void)sendEvent:(NSEvent*)theEvent; +- (void)closeAndRelease; +- (void)dealloc; +- (BOOL)isOpaque; +- (NSColor *)backgroundColor; +@property (nonatomic, readonly) QCocoaWindow *platformWindow; +@end + +typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow; + +@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol> @end +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); + +@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> @end +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); + +#endif // QNSWINDOW_H diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm new file mode 100644 index 0000000000..e846fa043c --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#include "qnswindow.h" +#include "qnswindowdelegate.h" +#include "qcocoawindow.h" +#include "qcocoahelpers.h" +#include "qcocoaeventdispatcher.h" + +#include <qpa/qwindowsysteminterface.h> +#include <qoperatingsystemversion.h> + +Q_LOGGING_CATEGORY(lcCocoaEvents, "qt.qpa.cocoa.events"); + +static bool isMouseEvent(NSEvent *ev) +{ + switch ([ev type]) { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + return true; + default: + return false; + } +} + +@implementation NSWindow (FullScreenProperty) + ++ (void)load +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); + } + ]; + [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + nil, OBJC_ASSOCIATION_RETAIN); + } + ]; +} + +- (BOOL)qt_fullScreen +{ + NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); + return [number boolValue]; +} +@end + +#define super USE_qt_objcDynamicSuper_INSTEAD + +@implementation QNSWindow + ++ (void)load +{ + const Class windowClass = [self class]; + const Class panelClass = [QNSPanel class]; + + unsigned int methodDescriptionsCount; + objc_method_description *methods = protocol_copyMethodDescriptionList( + objc_getProtocol("QNSWindowProtocol"), NO, YES, &methodDescriptionsCount); + + for (unsigned int i = 0; i < methodDescriptionsCount; ++i) { + objc_method_description method = methods[i]; + class_addMethod(panelClass, method.name, + class_getMethodImplementation(windowClass, method.name), + method.types); + } + + free(methods); +} + +- (QCocoaWindow *)platformWindow +{ + return qnsview_cast(self.contentView).platformWindow; +} + +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithString:qt_objcDynamicSuper()]; + +#ifndef QT_NO_DEBUG_STREAM + QString contentViewDescription; + QDebug debug(&contentViewDescription); + debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">"; + + NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1]; + [description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()]; +#endif + + return description; +} + +- (BOOL)canBecomeKeyWindow +{ + QCocoaWindow *pw = self.platformWindow; + if (!pw) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + + if ([self isKindOfClass:[QNSPanel class]]) { + // Only tool or dialog windows should become key: + Qt::WindowType type = pw->window()->type(); + if (type == Qt::Tool || type == Qt::Dialog) + return YES; + + return NO; + } else { + // The default implementation returns NO for title-bar less windows, + // override and return yes here to make sure popup windows such as + // the combobox popup can become the key window. + return YES; + } +} + +- (BOOL)canBecomeMainWindow +{ + BOOL canBecomeMain = YES; // By default, windows can become the main window + + // Windows with a transient parent (such as combobox popup windows) + // cannot become the main window: + QCocoaWindow *pw = self.platformWindow; + if (!pw || pw->window()->transientParent()) + canBecomeMain = NO; + + return canBecomeMain; +} + +- (BOOL)isOpaque +{ + return self.platformWindow ? + self.platformWindow->isOpaque() : qt_objcDynamicSuper(); +} + +/*! + Borderless windows need a transparent background + + Technically windows with NSTexturedBackgroundWindowMask (such + as windows with unified toolbars) need to draw the textured + background of the NSWindow, and can't have a transparent + background, but as NSBorderlessWindowMask is 0, you can't + have a window with NSTexturedBackgroundWindowMask that is + also borderless. +*/ +- (NSColor *)backgroundColor +{ + return self.styleMask == NSBorderlessWindowMask + ? [NSColor clearColor] : qt_objcDynamicSuper(); +} + +- (void)sendEvent:(NSEvent*)theEvent +{ + qCDebug(lcCocoaEvents) << "Sending" << theEvent << "to" << self; + + // We might get events for a NSWindow after the corresponding platform + // window has been deleted, as the NSWindow can outlive the QCocoaWindow + // e.g. if being retained by other parts of AppKit, or in an auto-release + // pool. We guard against this in QNSView as well, as not all callbacks + // come via events, but if they do there's no point in propagating them. + if (!self.platformWindow) + return; + + // Prevent deallocation of this NSWindow during event delivery, as we + // have logic further below that depends on the window being alive. + [[self retain] autorelease]; + + const char *eventType = object_getClassName(theEvent); + if (QWindowSystemInterface::handleNativeEvent(self.platformWindow->window(), + QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) { + return; + } + + qt_objcDynamicSuper(theEvent); + + if (!self.platformWindow) + return; // Platform window went away while processing event + + QCocoaWindow *pw = self.platformWindow; + if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + NSPoint loc = [theEvent locationInWindow]; + NSRect windowFrame = [self convertRectFromScreen:self.frame]; + NSRect contentFrame = self.contentView.frame; + if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) + [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; + } +} + +- (void)closeAndRelease +{ + qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; + + [self.delegate release]; + self.delegate = nil; + + [self close]; + [self release]; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" +- (void)dealloc +{ + qCDebug(lcQpaCocoaWindow) << "dealloc" << self; + qt_objcDynamicSuper(); +} +#pragma clang diagnostic pop + ++ (void)applicationActivationChanged:(NSNotification*)notification +{ + const id sender = self; + NSEnumerator<NSWindow*> *windowEnumerator = nullptr; + NSApplication *application = [NSApplication sharedApplication]; + +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) + if (__builtin_available(macOS 10.12, *)) { + // Unfortunately there's no NSWindowListOrderedBackToFront, + // so we have to manually reverse the order using an array. + NSMutableArray *windows = [[[NSMutableArray alloc] init] autorelease]; + [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *window, BOOL *) { + // For some reason AppKit will give us nil-windows, skip those + if (!window) + return; + + [(NSMutableArray*)windows addObject:window]; + } + ]; + + windowEnumerator = windows.reverseObjectEnumerator; + } else +#endif + { + // No way to get ordered list of windows, so fall back to unordered, + // list, which typically corresponds to window creation order. + windowEnumerator = application.windows.objectEnumerator; + } + + for (NSWindow *window in windowEnumerator) { + // We're meddling with normal and floating windows, so leave others alone + if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) + continue; + + // Windows that hide automatically will keep their NSFloatingWindowLevel, + // and hence be on top of the window stack. We don't want to affect these + // windows, as otherwise we might end up with key windows being ordered + // behind these auto-hidden windows when activating the application by + // clicking on a new tool window. + if (window.hidesOnDeactivate) + continue; + + if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { + QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow; + window.level = notification.name == NSApplicationWillResignActiveNotification ? + NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); + } + + // The documentation says that "when a window enters a new level, it’s ordered + // in front of all its peers in that level", but that doesn't seem to be the + // case in practice. To keep the order correct after meddling with the window + // levels, we explicitly order each window to the front. Since we are iterating + // the windows in back-to-front order, this is okey. The call also triggers AppKit + // to re-evaluate the level in relation to windows from other applications, + // working around an issue where our tool windows would stay on top of other + // application windows if activation was transferred to another application by + // clicking on it instead of via the application switcher or Dock. Finally, we + // do this re-ordering for all windows (except auto-hiding ones), otherwise we would + // end up triggering a bug in AppKit where the tool windows would disappear behind + // the application window. + [window orderFront:sender]; + } +} + +@end + +@implementation QNSPanel +// Implementation shared with QNSWindow, see +[QNSWindow load] above +@end + +#undef super diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 1224d138d9..cdecd86dfb 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -44,6 +44,8 @@ #include <qpa/qplatformscreen.h> #include <qpa/qwindowsysteminterface.h> +static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); + @implementation QNSWindowDelegate - (id)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow @@ -98,7 +100,10 @@ { Q_UNUSED(window); Q_UNUSED(menu); - return m_cocoaWindow && m_cocoaWindow->m_hasWindowFilePath; + + // Only pop up document path if the filename is non-empty. We allow whitespace, to + // allow faking a window icon by setting the file path to a single space character. + return !whitespaceRegex.exactMatch(m_cocoaWindow->window()->filePath()); } - (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard @@ -107,6 +112,9 @@ Q_UNUSED(event); Q_UNUSED(dragImageLocation); Q_UNUSED(pasteboard); - return m_cocoaWindow && m_cocoaWindow->m_hasWindowFilePath; + + // Only allow drag if the filename is non-empty. We allow whitespace, to + // allow faking a window icon by setting the file path to a single space. + return !whitespaceRegex.exactMatch(m_cocoaWindow->window()->filePath()); } @end diff --git a/src/plugins/platforms/direct2d/direct2d.pro b/src/plugins/platforms/direct2d/direct2d.pro index 3fe0d5e660..99b5491912 100644 --- a/src/plugins/platforms/direct2d/direct2d.pro +++ b/src/plugins/platforms/direct2d/direct2d.pro @@ -6,6 +6,7 @@ QT += \ fontdatabase_support-private theme_support-private qtConfig(accessibility): QT += accessibility_support-private +qtConfig(vulkan): QT += vulkan_support-private LIBS += -ldwmapi -ld2d1 -ld3d11 -ldwrite -lVersion -lgdi32 diff --git a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp index 9b4732eab4..f3efbea60b 100644 --- a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp @@ -41,6 +41,7 @@ #include <qpa/qwindowsysteminterface.h> #include <qpa/qplatformintegration.h> #include <private/qguiapplication_p.h> +#include <private/qwindow_p.h> #ifndef QT_NO_OPENGL # include <QtGui/private/qopenglcontext_p.h> # include <QtGui/QOpenGLContext> @@ -99,7 +100,6 @@ void QEglFSWindow::create() if (window()->type() == Qt::Desktop) { QRect fullscreenRect(QPoint(), screen()->availableGeometry().size()); - QPlatformWindow::setGeometry(fullscreenRect); QWindowSystemInterface::handleGeometryChange(window(), fullscreenRect); return; } @@ -235,21 +235,16 @@ void QEglFSWindow::setVisible(bool visible) void QEglFSWindow::setGeometry(const QRect &r) { - QRect rect; - bool forceFullscreen = m_flags.testFlag(HasNativeWindow); - if (forceFullscreen) + QRect rect = r; + if (m_flags.testFlag(HasNativeWindow)) rect = screen()->availableGeometry(); - else - rect = r; - const bool changed = rect != QPlatformWindow::geometry(); QPlatformWindow::setGeometry(rect); - // if we corrected the size, trigger a resize event - if (rect != r) - QWindowSystemInterface::handleGeometryChange(window(), rect, r); + QWindowSystemInterface::handleGeometryChange(window(), rect); - if (changed) + const QRect lastReportedGeometry = qt_window_private(window())->geometry; + if (rect != lastReportedGeometry) QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size())); } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.h index 513a5063fb..be5524cba3 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.h @@ -45,7 +45,7 @@ #include <QtCore/QLoggingCategory> #include <QtCore/QFunctionPointer> -typedef const char *(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC) (); +typedef QByteArray (EGLAPIENTRYP PFNQGSGETDISPLAYSPROC) (); typedef void (EGLAPIENTRYP PFNQGSSETDISPLAYPROC) (uint screen); QT_BEGIN_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.cpp index 4546088327..7654034f85 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.cpp @@ -110,6 +110,11 @@ uint QEglFSEmulatorScreen::id() const return m_id; } +QString QEglFSEmulatorScreen::name() const +{ + return m_description; +} + void QEglFSEmulatorScreen::initFromJsonObject(const QJsonObject &description) { QJsonValue value; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.h index 3e5113c9c2..c4994720fa 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorscreen.h @@ -62,6 +62,7 @@ public: qreal refreshRate() const override; Qt::ScreenOrientation nativeOrientation() const override; Qt::ScreenOrientation orientation() const override; + QString name() const override; uint id() const; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/eglfs_kms.pro b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/eglfs_kms.pro index e522c0ee1b..27c0af1f08 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/eglfs_kms.pro +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/eglfs_kms.pro @@ -4,7 +4,7 @@ PLUGIN_TYPE = egldeviceintegrations PLUGIN_CLASS_NAME = QEglFSKmsGbmIntegrationPlugin load(qt_plugin) -QT += core-private gui-private eglfsdeviceintegration-private eglfs_kms_support-private kms_support-private +QT += core-private gui-private eglfsdeviceintegration-private eglfs_kms_support-private kms_support-private edid_support-private INCLUDEPATH += $$PWD/../../api $$PWD/../eglfs_kms_support diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp index 19790e5c45..800118362d 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp @@ -204,7 +204,7 @@ void QEglFSKmsGbmCursor::changeCursor(QCursor *windowCursor, QWindow *window) painter.drawImage(0, 0, *m_cursorImage.image()); painter.end(); - gbm_bo_write(m_bo, cursorImage.constBits(), cursorImage.byteCount()); + gbm_bo_write(m_bo, cursorImage.constBits(), cursorImage.sizeInBytes()); uint32_t handle = gbm_bo_get_handle(m_bo).u32; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp index b6cdcf92b6..058791e473 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp @@ -43,9 +43,11 @@ #include "qeglfskmsgbmdevice.h" #include "qeglfskmsgbmscreen.h" #include "qeglfskmsgbmcursor.h" +#include "private/qeglfswindow_p.h" #include "private/qeglfscursor_p.h" #include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h> +#include <QtEglSupport/private/qeglconvenience_p.h> #include <QtCore/QLoggingCategory> #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> @@ -67,20 +69,35 @@ QEglFSKmsGbmIntegration::QEglFSKmsGbmIntegration() qCDebug(qLcEglfsKmsDebug, "New DRM/KMS via GBM integration created"); } -EGLNativeWindowType QEglFSKmsGbmIntegration::createNativeWindow(QPlatformWindow *platformWindow, - const QSize &size, - const QSurfaceFormat &format) +#ifndef EGL_EXT_platform_base +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); +#endif + +#ifndef EGL_PLATFORM_GBM_KHR +#define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif + +EGLDisplay QEglFSKmsGbmIntegration::createDisplay(EGLNativeDisplayType nativeDisplay) { - Q_UNUSED(size); - Q_UNUSED(format); + qCDebug(qLcEglfsKmsDebug, "Querying EGLDisplay"); + EGLDisplay display; + + PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay = nullptr; + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (extensions && (strstr(extensions, "EGL_KHR_platform_gbm") || strstr(extensions, "EGL_MESA_platform_gbm"))) { + getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>( + eglGetProcAddress("eglGetPlatformDisplayEXT")); + } - QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(platformWindow->screen()); - if (screen->surface()) { - qWarning("Only single window per screen supported!"); - return 0; + if (getPlatformDisplay) { + display = getPlatformDisplay(EGL_PLATFORM_GBM_KHR, nativeDisplay, nullptr); + } else { + qCDebug(qLcEglfsKmsDebug, "No eglGetPlatformDisplay for GBM, falling back to eglGetDisplay"); + display = eglGetDisplay(nativeDisplay); } - return reinterpret_cast<EGLNativeWindowType>(screen->createSurface()); + return display; } EGLNativeWindowType QEglFSKmsGbmIntegration::createNativeOffscreenWindow(const QSurfaceFormat &format) @@ -143,4 +160,49 @@ QKmsDevice *QEglFSKmsGbmIntegration::createDevice() return new QEglFSKmsGbmDevice(screenConfig(), path); } +class QEglFSKmsGbmWindow : public QEglFSWindow +{ +public: + QEglFSKmsGbmWindow(QWindow *w, const QEglFSKmsGbmIntegration *integration) + : QEglFSWindow(w) + , m_integration(integration) + {} + void resetSurface() override; + const QEglFSKmsGbmIntegration *m_integration; +}; + +void QEglFSKmsGbmWindow::resetSurface() +{ + QEglFSKmsGbmScreen *gbmScreen = static_cast<QEglFSKmsGbmScreen *>(screen()); + if (gbmScreen->surface()) { + qWarning("Only single window per screen supported!"); + return; + } + + EGLDisplay display = gbmScreen->display(); + QSurfaceFormat platformFormat = m_integration->surfaceFormatFor(window()->requestedFormat()); + m_config = QEglFSDeviceIntegration::chooseConfig(display, platformFormat); + m_format = q_glFormatFromConfig(display, m_config, platformFormat); + m_window = reinterpret_cast<EGLNativeWindowType>(gbmScreen->createSurface()); + + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface = nullptr; + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (extensions && (strstr(extensions, "EGL_KHR_platform_gbm") || strstr(extensions, "EGL_MESA_platform_gbm"))) { + createPlatformWindowSurface = reinterpret_cast<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>( + eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT")); + } + + if (createPlatformWindowSurface) { + m_surface = createPlatformWindowSurface(display, m_config, reinterpret_cast<void *>(m_window), nullptr); + } else { + qCDebug(qLcEglfsKmsDebug, "No eglCreatePlatformWindowSurface for GBM, falling back to eglCreateWindowSurface"); + m_surface = eglCreateWindowSurface(display, m_config, m_window, nullptr); + } +} + +QEglFSWindow *QEglFSKmsGbmIntegration::createWindow(QWindow *window) const +{ + return new QEglFSKmsGbmWindow(window, this); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.h index 38f132d72e..71f232abf9 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.h @@ -55,14 +55,13 @@ class QEglFSKmsGbmIntegration : public QEglFSKmsIntegration public: QEglFSKmsGbmIntegration(); - EGLNativeWindowType createNativeWindow(QPlatformWindow *platformWindow, - const QSize &size, - const QSurfaceFormat &format) override; + EGLDisplay createDisplay(EGLNativeDisplayType nativeDisplay) override; EGLNativeWindowType createNativeOffscreenWindow(const QSurfaceFormat &format) override; void destroyNativeWindow(EGLNativeWindowType window) override; QPlatformCursor *createCursor(QPlatformScreen *screen) const override; void presentBuffer(QPlatformSurface *surface) override; + QEglFSWindow *createWindow(QWindow *window) const override; protected: QKmsDevice *createDevice() override; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/eglfs_kms_egldevice.pro b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/eglfs_kms_egldevice.pro index a2dc9c4a50..36f037ac6c 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/eglfs_kms_egldevice.pro +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/eglfs_kms_egldevice.pro @@ -1,6 +1,6 @@ TARGET = qeglfs-kms-egldevice-integration -QT += core-private gui-private eglfsdeviceintegration-private eglfs_kms_support-private kms_support-private +QT += core-private gui-private eglfsdeviceintegration-private eglfs_kms_support-private kms_support-private edid_support-private INCLUDEPATH += $$PWD/../../api $$PWD/../eglfs_kms_support diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevice.cpp index 0a66a897a1..cca413ff2d 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevice.cpp @@ -77,9 +77,9 @@ void QEglFSKmsEglDevice::close() setFd(-1); } -EGLNativeDisplayType QEglFSKmsEglDevice::nativeDisplay() const +void *QEglFSKmsEglDevice::nativeDisplay() const { - return reinterpret_cast<EGLNativeDisplayType>(m_devInt->eglDevice()); + return m_devInt->eglDevice(); } QPlatformScreen *QEglFSKmsEglDevice::createScreen(const QKmsOutput &output) diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/eglfs_kms_support.pro b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/eglfs_kms_support.pro index 3c0a0ce30f..88cf6db33b 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/eglfs_kms_support.pro +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/eglfs_kms_support.pro @@ -2,7 +2,7 @@ TARGET = QtEglFsKmsSupport CONFIG += no_module_headers internal_module load(qt_module) -QT += core-private gui-private eglfsdeviceintegration-private kms_support-private +QT += core-private gui-private eglfsdeviceintegration-private kms_support-private edid_support-private INCLUDEPATH += $$PWD/../../api diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp index 3951f46a82..734f5cd611 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2017 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 Pelagicore AG ** Contact: https://www.qt.io/licensing/ @@ -69,13 +69,29 @@ private: }; QEglFSKmsScreen::QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output) - : QEglFSScreen(eglGetDisplay((EGLNativeDisplayType) device->nativeDisplay())) + : QEglFSScreen(static_cast<QEglFSIntegration *>(QGuiApplicationPrivate::platformIntegration())->display()) , m_device(device) , m_output(output) , m_powerState(PowerStateOn) , m_interruptHandler(new QEglFSKmsInterruptHandler(this)) { m_siblings << this; // gets overridden later + + if (m_output.edid_blob) { + QByteArray edid(reinterpret_cast<const char *>(m_output.edid_blob->data), m_output.edid_blob->length); + if (m_edid.parse(edid)) + qCDebug(qLcEglfsKmsDebug, "EDID data for output \"%s\": identifier '%s', manufacturer '%s', model '%s', serial '%s', physical size: %.2fx%.2f", + name().toLatin1().constData(), + m_edid.identifier.toLatin1().constData(), + m_edid.manufacturer.toLatin1().constData(), + m_edid.model.toLatin1().constData(), + m_edid.serialNumber.toLatin1().constData(), + m_edid.physicalSize.width(), m_edid.physicalSize.height()); + else + qCDebug(qLcEglfsKmsDebug) << "Failed to parse EDID data for output" << name(); // keep this debug, not warning + } else { + qCDebug(qLcEglfsKmsDebug) << "No EDID data for output" << name(); + } } QEglFSKmsScreen::~QEglFSKmsScreen() @@ -146,6 +162,21 @@ QString QEglFSKmsScreen::name() const return m_output.name; } +QString QEglFSKmsScreen::manufacturer() const +{ + return m_edid.manufacturer; +} + +QString QEglFSKmsScreen::model() const +{ + return m_edid.model.isEmpty() ? m_edid.identifier : m_edid.model; +} + +QString QEglFSKmsScreen::serialNumber() const +{ + return m_edid.serialNumber; +} + void QEglFSKmsScreen::destroySurface() { } @@ -173,6 +204,28 @@ qreal QEglFSKmsScreen::refreshRate() const return refresh > 0 ? refresh : 60; } +QVector<QPlatformScreen::Mode> QEglFSKmsScreen::modes() const +{ + QVector<QPlatformScreen::Mode> list; + list.reserve(m_output.modes.size()); + + for (const drmModeModeInfo &info : qAsConst(m_output.modes)) + list.append({QSize(info.hdisplay, info.vdisplay), + qreal(info.vrefresh > 0 ? info.vrefresh : 60)}); + + return list; +} + +int QEglFSKmsScreen::currentMode() const +{ + return m_output.mode; +} + +int QEglFSKmsScreen::preferredMode() const +{ + return m_output.preferred_mode; +} + QPlatformScreen::SubpixelAntialiasingType QEglFSKmsScreen::subpixelAntialiasingTypeHint() const { return m_output.subpixelAntialiasingTypeHint(); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h index 80bbb0c7f1..4e09929189 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h @@ -47,6 +47,7 @@ #include <QtCore/QMutex> #include <QtKmsSupport/private/qkmsdevice_p.h> +#include <QtEdidSupport/private/qedidparser_p.h> QT_BEGIN_NAMESPACE @@ -72,11 +73,20 @@ public: QString name() const override; + QString manufacturer() const override; + QString model() const override; + QString serialNumber() const override; + qreal refreshRate() const override; QList<QPlatformScreen *> virtualSiblings() const override { return m_siblings; } void setVirtualSiblings(QList<QPlatformScreen *> sl) { m_siblings = sl; } + QVector<QPlatformScreen::Mode> modes() const override; + + int currentMode() const override; + int preferredMode() const override; + QKmsDevice *device() const { return m_device; } void destroySurface(); @@ -97,6 +107,7 @@ protected: QKmsDevice *m_device; QKmsOutput m_output; + QEdidParser m_edid; QPoint m_pos; QList<QPlatformScreen *> m_siblings; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp index 64d0d9b515..2e84915c80 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp @@ -63,83 +63,12 @@ private: QAtomicInt running; -static Qt::MouseButtons translateMouseButtons(int s) -{ - Qt::MouseButtons ret = 0; - if (s & XCB_BUTTON_MASK_1) - ret |= Qt::LeftButton; - if (s & XCB_BUTTON_MASK_2) - ret |= Qt::MidButton; - if (s & XCB_BUTTON_MASK_3) - ret |= Qt::RightButton; - return ret; -} - -static Qt::MouseButton translateMouseButton(xcb_button_t s) -{ - switch (s) { - case 1: return Qt::LeftButton; - case 2: return Qt::MidButton; - case 3: return Qt::RightButton; - // Button values 4-7 were already handled as Wheel events, and won't occur here. - case 8: return Qt::BackButton; // Also known as Qt::ExtraButton1 - case 9: return Qt::ForwardButton; // Also known as Qt::ExtraButton2 - case 10: return Qt::ExtraButton3; - case 11: return Qt::ExtraButton4; - case 12: return Qt::ExtraButton5; - case 13: return Qt::ExtraButton6; - case 14: return Qt::ExtraButton7; - case 15: return Qt::ExtraButton8; - case 16: return Qt::ExtraButton9; - case 17: return Qt::ExtraButton10; - case 18: return Qt::ExtraButton11; - case 19: return Qt::ExtraButton12; - case 20: return Qt::ExtraButton13; - case 21: return Qt::ExtraButton14; - case 22: return Qt::ExtraButton15; - case 23: return Qt::ExtraButton16; - case 24: return Qt::ExtraButton17; - case 25: return Qt::ExtraButton18; - case 26: return Qt::ExtraButton19; - case 27: return Qt::ExtraButton20; - case 28: return Qt::ExtraButton21; - case 29: return Qt::ExtraButton22; - case 30: return Qt::ExtraButton23; - case 31: return Qt::ExtraButton24; - default: return Qt::NoButton; - } -} - void EventReader::run() { - Qt::MouseButtons buttons; - xcb_generic_event_t *event = nullptr; while (running.load() && (event = xcb_wait_for_event(m_integration->connection()))) { uint response_type = event->response_type & ~0x80; switch (response_type) { - case XCB_BUTTON_PRESS: { - xcb_button_press_event_t *press = (xcb_button_press_event_t *)event; - QPoint p(press->event_x, press->event_y); - buttons = (buttons & ~0x7) | translateMouseButtons(press->state); - buttons |= translateMouseButton(press->detail); - QWindowSystemInterface::handleMouseEvent(0, press->time, p, p, buttons); - break; - } - case XCB_BUTTON_RELEASE: { - xcb_button_release_event_t *release = (xcb_button_release_event_t *)event; - QPoint p(release->event_x, release->event_y); - buttons = (buttons & ~0x7) | translateMouseButtons(release->state); - buttons &= ~translateMouseButton(release->detail); - QWindowSystemInterface::handleMouseEvent(0, release->time, p, p, buttons); - break; - } - case XCB_MOTION_NOTIFY: { - xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)event; - QPoint p(motion->event_x, motion->event_y); - QWindowSystemInterface::handleMouseEvent(0, motion->time, p, p, buttons); - break; - } case XCB_CLIENT_MESSAGE: { xcb_client_message_event_t *client = (xcb_client_message_event_t *) event; const xcb_atom_t *atoms = m_integration->atoms(); diff --git a/src/plugins/platforms/haiku/qhaikuwindow.cpp b/src/plugins/platforms/haiku/qhaikuwindow.cpp index 4634012eda..4bea7f7ff6 100644 --- a/src/plugins/platforms/haiku/qhaikuwindow.cpp +++ b/src/plugins/platforms/haiku/qhaikuwindow.cpp @@ -205,26 +205,23 @@ void QHaikuWindow::requestActivateWindow() m_window->Activate(true); } -void QHaikuWindow::setWindowState(Qt::WindowState state) +void QHaikuWindow::setWindowState(Qt::WindowStates state) { if (m_windowState == state) return; - const Qt::WindowState oldState = m_windowState; + const Qt::WindowStates oldState = m_windowState; m_windowState = state; - if (m_windowState == Qt::WindowMaximized) { - m_window->zoomByQt(); - } else if (m_windowState == Qt::WindowMinimized) { + if (m_windowState & Qt::WindowMinimized) m_window->Minimize(true); - } else if (m_windowState == Qt::WindowNoState) { - if (oldState == Qt::WindowMaximized) - m_window->zoomByQt(); // undo zoom - - if (oldState == Qt::WindowMinimized) - m_window->Minimize(false); // undo minimize - } + else if (m_windowState & Qt::WindowMaximized) + m_window->zoomByQt(); + else if (oldState & Qt::WindowMinimized) + m_window->Minimize(false); // undo minimize + else if (oldState & Qt::WindowMaximized) + m_window->zoomByQt(); // undo zoom } void QHaikuWindow::setWindowFlags(Qt::WindowFlags flags) @@ -312,7 +309,6 @@ void QHaikuWindow::haikuWindowMoved(const QPoint &pos) { const QRect newGeometry(pos, geometry().size()); - QPlatformWindow::setGeometry(newGeometry); QWindowSystemInterface::handleGeometryChange(window(), newGeometry); QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), newGeometry.size())); } @@ -321,7 +317,6 @@ void QHaikuWindow::haikuWindowResized(const QSize &size, bool zoomInProgress) { const QRect newGeometry(geometry().topLeft(), size); - QPlatformWindow::setGeometry(newGeometry); QWindowSystemInterface::handleGeometryChange(window(), newGeometry); QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), newGeometry.size())); diff --git a/src/plugins/platforms/haiku/qhaikuwindow.h b/src/plugins/platforms/haiku/qhaikuwindow.h index 75403fb421..5bfb99e532 100644 --- a/src/plugins/platforms/haiku/qhaikuwindow.h +++ b/src/plugins/platforms/haiku/qhaikuwindow.h @@ -94,7 +94,7 @@ public: BWindow* nativeHandle() const; void requestActivateWindow() Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; + void setWindowState(Qt::WindowStates state) Q_DECL_OVERRIDE; void setWindowFlags(Qt::WindowFlags flags) Q_DECL_OVERRIDE; void setWindowTitle(const QString &title) Q_DECL_OVERRIDE; @@ -120,7 +120,7 @@ private Q_SLOTS: void haikuDrawRequest(const QRect &rect); private: - Qt::WindowState m_windowState; + Qt::WindowStates m_windowState; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosbackingstore.h b/src/plugins/platforms/ios/qiosbackingstore.h index 3954347471..e6b890251a 100644 --- a/src/plugins/platforms/ios/qiosbackingstore.h +++ b/src/plugins/platforms/ios/qiosbackingstore.h @@ -55,9 +55,6 @@ public: ~QIOSBackingStore(); void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - -private: - QOpenGLContext *m_context; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosbackingstore.mm b/src/plugins/platforms/ios/qiosbackingstore.mm index 74229684e3..db4dd81b2e 100644 --- a/src/plugins/platforms/ios/qiosbackingstore.mm +++ b/src/plugins/platforms/ios/qiosbackingstore.mm @@ -55,7 +55,6 @@ QT_BEGIN_NAMESPACE */ QIOSBackingStore::QIOSBackingStore(QWindow *window) : QRasterBackingStore(window) - , m_context(new QOpenGLContext) { // We use the surface both for raster operations and for GL drawing (when // we blit the raster image), so the type needs to cover both use cases. @@ -64,22 +63,10 @@ QIOSBackingStore::QIOSBackingStore(QWindow *window) Q_ASSERT_X(window->surfaceType() != QSurface::OpenGLSurface, "QIOSBackingStore", "QBackingStore on iOS can only be used with raster-enabled surfaces."); - - m_context->setFormat(window->requestedFormat()); - m_context->setScreen(window->screen()); - Q_ASSERT(QOpenGLContext::globalShareContext()); - m_context->setShareContext(QOpenGLContext::globalShareContext()); - m_context->create(); } QIOSBackingStore::~QIOSBackingStore() { - // We're using composeAndFlush from QPlatformBackingStore, which - // need to clean up any textures in its destructor, so make the - // context current and keep it alive until QPlatformBackingStore - // has cleaned up everything. - m_context->makeCurrent(window()); - m_context->deleteLater(); } void QIOSBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) @@ -98,7 +85,7 @@ void QIOSBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoin } static QPlatformTextureList emptyTextureList; - composeAndFlush(window, region, offset, &emptyTextureList, m_context, false); + composeAndFlush(window, region, offset, &emptyTextureList, false); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosclipboard.mm b/src/plugins/platforms/ios/qiosclipboard.mm index 6a585c9052..15deb3332e 100644 --- a/src/plugins/platforms/ios/qiosclipboard.mm +++ b/src/plugins/platforms/ios/qiosclipboard.mm @@ -232,7 +232,7 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode) mimeDataAsVariant = mimeData->imageData(); } else if (mimeData->hasUrls()) { QVariantList urlList; - for (const QUrl &url : mimeData->urls()) + for (QUrl url : mimeData->urls()) urlList << url; mimeDataAsVariant = QVariant(urlList); } else { diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm index 7194d5bed1..de2c30cdfb 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.mm +++ b/src/plugins/platforms/ios/qioseventdispatcher.mm @@ -374,7 +374,7 @@ static bool rootLevelRunLoopIntegration() // We treat applicationWillTerminate as SIGTERM, even if it can't be ignored, // and follow the bash convention of encoding the signal number in the upper // four bits of the exit code (exit(3) will only pass on the lower 8 bits). -static const char kApplicationWillTerminateExitCode = SIGTERM | 0x80; +static const char kApplicationWillTerminateExitCode = char(SIGTERM | 0x80); + (void)applicationWillTerminate { diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h index 1d53734096..522cc032ff 100644 --- a/src/plugins/platforms/ios/qiosintegration.h +++ b/src/plugins/platforms/ios/qiosintegration.h @@ -58,8 +58,6 @@ class QIOSServices; class QIOSIntegration : public QPlatformNativeInterface, public QPlatformIntegration { Q_OBJECT - Q_PROPERTY(bool debugWindowManagement READ debugWindowManagement WRITE setDebugWindowManagement); - public: QIOSIntegration(); ~QIOSIntegration(); @@ -104,9 +102,6 @@ public: void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) Q_DECL_OVERRIDE; - void setDebugWindowManagement(bool); - bool debugWindowManagement() const; - QFactoryLoader *optionalPlugins() { return m_optionalPlugins; } QIOSApplicationState applicationState; @@ -124,8 +119,6 @@ private: #ifndef Q_OS_TVOS QIOSTextInputOverlay m_textInputOverlay; #endif - - bool m_debugWindowManagement; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm index 482f996943..94a30eb931 100644 --- a/src/plugins/platforms/ios/qiosintegration.mm +++ b/src/plugins/platforms/ios/qiosintegration.mm @@ -85,7 +85,6 @@ QIOSIntegration::QIOSIntegration() , m_platformServices(new QIOSServices) , m_accessibility(0) , m_optionalPlugins(new QFactoryLoader(QIosOptionalPluginInterface_iid, QLatin1String("/platforms/darwin"))) - , m_debugWindowManagement(false) { if (Q_UNLIKELY(![UIApplication sharedApplication])) { qFatal("Error: You are creating QApplication before calling UIApplicationMain.\n" \ @@ -94,10 +93,6 @@ QIOSIntegration::QIOSIntegration() "'applicationDidFinishLaunching' inside your UIApplication delegate.\n"); } - // The backingstore needs a global share context in order to support composition in - // QPlatformBackingStore. - qApp->setAttribute(Qt::AA_ShareOpenGLContexts, true); - // Set current directory to app bundle folder QDir::setCurrent(QString::fromUtf8([[[NSBundle mainBundle] bundlePath] UTF8String])); @@ -117,7 +112,7 @@ QIOSIntegration::QIOSIntegration() m_touchDevice = new QTouchDevice; m_touchDevice->setType(QTouchDevice::TouchScreen); QTouchDevice::Capabilities touchCapabilities = QTouchDevice::Position | QTouchDevice::NormalizedPosition; - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::IOS, 9)) { + if (__builtin_available(iOS 9, *)) { if (mainScreen.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) touchCapabilities |= QTouchDevice::Pressure; } @@ -322,16 +317,6 @@ void *QIOSIntegration::nativeResourceForWindow(const QByteArray &resource, QWind return 0; } -void QIOSIntegration::setDebugWindowManagement(bool enabled) -{ - m_debugWindowManagement = enabled; -} - -bool QIOSIntegration::debugWindowManagement() const -{ - return m_debugWindowManagement; -} - // --------------------------------------------------------- #include "moc_qiosintegration.cpp" diff --git a/src/plugins/platforms/ios/qiosmenu.h b/src/plugins/platforms/ios/qiosmenu.h index b7371a5f49..61cadab56d 100644 --- a/src/plugins/platforms/ios/qiosmenu.h +++ b/src/plugins/platforms/ios/qiosmenu.h @@ -56,9 +56,6 @@ class QIOSMenuItem : public QPlatformMenuItem public: QIOSMenuItem(); - void setTag(quintptr tag) Q_DECL_OVERRIDE; - quintptr tag()const Q_DECL_OVERRIDE; - void setText(const QString &text) Q_DECL_OVERRIDE; void setIcon(const QIcon &) Q_DECL_OVERRIDE {} void setMenu(QPlatformMenu *) Q_DECL_OVERRIDE; @@ -74,7 +71,6 @@ public: void setEnabled(bool enabled) Q_DECL_OVERRIDE; void setIconSize(int) Q_DECL_OVERRIDE {} - quintptr m_tag; bool m_visible; QString m_text; MenuRole m_role; @@ -97,9 +93,6 @@ public: void syncMenuItem(QPlatformMenuItem *) Q_DECL_OVERRIDE; void syncSeparatorsCollapsible(bool) Q_DECL_OVERRIDE {} - void setTag(quintptr tag) Q_DECL_OVERRIDE; - quintptr tag()const Q_DECL_OVERRIDE; - void setText(const QString &) Q_DECL_OVERRIDE; void setIcon(const QIcon &) Q_DECL_OVERRIDE {} void setEnabled(bool enabled) Q_DECL_OVERRIDE; @@ -121,7 +114,6 @@ protected: bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; private: - quintptr m_tag; bool m_enabled; bool m_visible; QString m_text; diff --git a/src/plugins/platforms/ios/qiosmenu.mm b/src/plugins/platforms/ios/qiosmenu.mm index 01cb3badea..6c70676a31 100644 --- a/src/plugins/platforms/ios/qiosmenu.mm +++ b/src/plugins/platforms/ios/qiosmenu.mm @@ -259,7 +259,6 @@ static NSString *const kSelectorPrefix = @"_qtMenuItem_"; QIOSMenuItem::QIOSMenuItem() : QPlatformMenuItem() - , m_tag(0) , m_visible(true) , m_text(QString()) , m_role(MenuRole(0)) @@ -269,16 +268,6 @@ QIOSMenuItem::QIOSMenuItem() { } -void QIOSMenuItem::setTag(quintptr tag) -{ - m_tag = tag; -} - -quintptr QIOSMenuItem::tag() const -{ - return m_tag; -} - void QIOSMenuItem::setText(const QString &text) { m_text = QPlatformTheme::removeMnemonics(text); @@ -319,7 +308,6 @@ void QIOSMenuItem::setEnabled(bool enabled) QIOSMenu::QIOSMenu() : QPlatformMenu() - , m_tag(0) , m_enabled(true) , m_visible(false) , m_text(QString()) @@ -371,16 +359,6 @@ void QIOSMenu::syncMenuItem(QPlatformMenuItem *) } } -void QIOSMenu::setTag(quintptr tag) -{ - m_tag = tag; -} - -quintptr QIOSMenu::tag() const -{ - return m_tag; -} - void QIOSMenu::setText(const QString &text) { m_text = text; diff --git a/src/plugins/platforms/ios/qiosmessagedialog.mm b/src/plugins/platforms/ios/qiosmessagedialog.mm index 4f0c667861..5507f13de7 100644 --- a/src/plugins/platforms/ios/qiosmessagedialog.mm +++ b/src/plugins/platforms/ios/qiosmessagedialog.mm @@ -39,7 +39,6 @@ #import <UIKit/UIKit.h> -#include <QtCore/qoperatingsystemversion.h> #include <QtGui/qwindow.h> #include <QtGui/private/qguiapplication_p.h> #include <qpa/qplatformtheme.h> @@ -109,8 +108,7 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win Q_UNUSED(windowFlags); if (m_alertController // Ensure that the dialog is not showing already || !options() // Some message dialogs don't have options (QErrorMessage) - || windowModality == Qt::NonModal // We can only do modal dialogs - || QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 8)) // API limitation + || windowModality == Qt::NonModal) // We can only do modal dialogs return false; m_alertController = [[UIAlertController diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index 5717483a81..c394592d76 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -45,7 +45,6 @@ #include "qiosapplicationdelegate.h" #include "qiosviewcontroller.h" #include "quiview.h" -#include <QtCore/qoperatingsystemversion.h> #include <QtGui/private/qwindow_p.h> #include <private/qcoregraphics_p.h> @@ -330,14 +329,6 @@ void QIOSScreen::updateProperties() if (m_uiScreen == [UIScreen mainScreen]) { Qt::ScreenOrientation statusBarOrientation = toQtScreenOrientation(UIDeviceOrientation([UIApplication sharedApplication].statusBarOrientation)); - if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 8)) { - // On iOS < 8.0 the UIScreen geometry is always in portait, and the system applies - // the screen rotation to the root view-controller's view instead of directly to the - // screen, like iOS 8 and above does. - m_geometry = mapBetween(Qt::PortraitOrientation, statusBarOrientation, m_geometry); - m_availableGeometry = transformBetween(Qt::PortraitOrientation, statusBarOrientation, m_geometry).mapRect(m_availableGeometry); - } - QIOSViewController *qtViewController = [m_uiWindow.rootViewController isKindOfClass:[QIOSViewController class]] ? static_cast<QIOSViewController *>(m_uiWindow.rootViewController) : nil; @@ -357,20 +348,15 @@ void QIOSScreen::updateProperties() #endif if (m_geometry != previousGeometry) { - QRectF physicalGeometry; - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::IOS, 8)) { - // We can't use the primaryOrientation of screen(), as we haven't reported the new geometry yet - Qt::ScreenOrientation primaryOrientation = m_geometry.width() >= m_geometry.height() ? - Qt::LandscapeOrientation : Qt::PortraitOrientation; - - // On iPhone 6+ devices, or when display zoom is enabled, the render buffer is scaled - // before being output on the physical display. We have to take this into account when - // computing the physical size. Note that unlike the native bounds, the physical size - // follows the primary orientation of the screen. - physicalGeometry = mapBetween(nativeOrientation(), primaryOrientation, QRectF::fromCGRect(m_uiScreen.nativeBounds).toRect()); - } else { - physicalGeometry = QRectF(0, 0, m_geometry.width() * devicePixelRatio(), m_geometry.height() * devicePixelRatio()); - } + // We can't use the primaryOrientation of screen(), as we haven't reported the new geometry yet + Qt::ScreenOrientation primaryOrientation = m_geometry.width() >= m_geometry.height() ? + Qt::LandscapeOrientation : Qt::PortraitOrientation; + + // On iPhone 6+ devices, or when display zoom is enabled, the render buffer is scaled + // before being output on the physical display. We have to take this into account when + // computing the physical size. Note that unlike the native bounds, the physical size + // follows the primary orientation of the screen. + const QRectF physicalGeometry = mapBetween(nativeOrientation(), primaryOrientation, QRectF::fromCGRect(m_uiScreen.nativeBounds).toRect()); static const qreal millimetersPerInch = 25.4; m_physicalSize = physicalGeometry.size() / m_physicalDpi * millimetersPerInch; diff --git a/src/plugins/platforms/ios/qiosservices.mm b/src/plugins/platforms/ios/qiosservices.mm index 0ecc8e123f..3c44e1d7d6 100644 --- a/src/plugins/platforms/ios/qiosservices.mm +++ b/src/plugins/platforms/ios/qiosservices.mm @@ -55,11 +55,13 @@ bool QIOSServices::openUrl(const QUrl &url) return openDocument(url); NSURL *nsUrl = url.toNSURL(); + UIApplication *application = [UIApplication sharedApplication]; - if (![[UIApplication sharedApplication] canOpenURL:nsUrl]) + if (![application canOpenURL:nsUrl]) return false; - return [[UIApplication sharedApplication] openURL:nsUrl]; + [application openURL:nsUrl options:@{} completionHandler:nil]; + return true; } bool QIOSServices::openDocument(const QUrl &url) diff --git a/src/plugins/platforms/ios/qiostextinputoverlay.mm b/src/plugins/platforms/ios/qiostextinputoverlay.mm index 9b97ce17bb..bb9fe4d58f 100644 --- a/src/plugins/platforms/ios/qiostextinputoverlay.mm +++ b/src/plugins/platforms/ios/qiostextinputoverlay.mm @@ -229,12 +229,6 @@ static void executeBlockWithoutAnimation(Block block) borderLayer.cornerRadius = cornerRadius; borderLayer.borderColor = [[UIColor lightGrayColor] CGColor]; [self addSublayer:borderLayer]; - - if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 7)) { - // [UIView snapshotViewAfterScreenUpdates:] is available since iOS 7.0. - // Just silently ignore showing the loupe for older versions. - self.hidden = YES; - } } return self; @@ -278,9 +272,6 @@ static void executeBlockWithoutAnimation(Block block) - (void)display { - if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 7)) - return; - // Take a snapshow of the target view, magnify the area around the focal // point, and add the snapshow layer as a child of the container layer // to make it look like a loupe. Then place this layer at the position of diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 33a0b6a42e..b029c49a67 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -238,7 +238,7 @@ self.inputAccessoryView = [[[WrapperView alloc] initWithView:accessoryView] autorelease]; #ifndef Q_OS_TVOS - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_9_0) { + if (__builtin_available(iOS 9, *)) { if (platformData.value(kImePlatformDataHideShortcutsBar).toBool()) { // According to the docs, leadingBarButtonGroups/trailingBarButtonGroups should be set to nil to hide the shortcuts bar. // However, starting with iOS 10, the API has been surrounded with NS_ASSUME_NONNULL, which contradicts this and causes @@ -879,9 +879,10 @@ - (UITextPosition *)closestPositionToPoint:(CGPoint)point { - // No API in Qt for determining this. Use sensible default instead: - Q_UNUSED(point); - return [QUITextPosition positionWithIndex:[self currentImeState:Qt::ImCursorPosition].toInt()]; + QPointF p = QPointF::fromCGPoint(point); + const QTransform mapToLocal = QGuiApplication::inputMethod()->inputItemTransform().inverted(); + int textPos = QInputMethod::queryFocusObject(Qt::ImCursorPosition, p * mapToLocal).toInt(); + return [QUITextPosition positionWithIndex:textPos]; } - (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index a9fdfaf9f1..a7663b9e94 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -77,8 +77,7 @@ if (!(self = [super init])) return nil; - QIOSIntegration *iosIntegration = QIOSIntegration::instance(); - if (iosIntegration && iosIntegration->debugWindowManagement()) { + if (qEnvironmentVariableIntValue("QT_IOS_DEBUG_WINDOW_MANAGEMENT")) { static UIImage *gridPattern = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -196,8 +195,8 @@ return; // Re-apply window states to update geometry - if (window->windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized)) - window->handle()->setWindowState(window->windowState()); + if (window->windowStates() & (Qt::WindowFullScreen | Qt::WindowMaximized)) + window->handle()->setWindowState(window->windowStates()); } // Even if the root view controller has both wantsFullScreenLayout and diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h index 85c60f61df..14fa2084c9 100644 --- a/src/plugins/platforms/ios/qioswindow.h +++ b/src/plugins/platforms/ios/qioswindow.h @@ -62,7 +62,7 @@ public: void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; + void setWindowState(Qt::WindowStates state) Q_DECL_OVERRIDE; void setParent(const QPlatformWindow *window) Q_DECL_OVERRIDE; void handleContentOrientationChange(Qt::ScreenOrientation orientation) Q_DECL_OVERRIDE; void setVisible(bool visible) Q_DECL_OVERRIDE; diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm index 4ce73d7b5d..38136c05db 100644 --- a/src/plugins/platforms/ios/qioswindow.mm +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -73,7 +73,7 @@ QIOSWindow::QIOSWindow(QWindow *window) m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(), screen()->availableGeometry().width(), screen()->availableGeometry().height()); - setWindowState(window->windowState()); + setWindowState(window->windowStates()); setOpacity(window->opacity()); Qt::ScreenOrientation initialOrientation = window->contentOrientation(); @@ -236,7 +236,7 @@ bool QIOSWindow::isExposed() const && window()->isVisible() && !window()->geometry().isEmpty(); } -void QIOSWindow::setWindowState(Qt::WindowState state) +void QIOSWindow::setWindowState(Qt::WindowStates state) { // Update the QWindow representation straight away, so that // we can update the statusbar visibility based on the new @@ -246,12 +246,9 @@ void QIOSWindow::setWindowState(Qt::WindowState state) if (window()->isTopLevel() && window()->isVisible() && window()->isActive()) [m_view.qtViewController updateProperties]; - switch (state) { - case Qt::WindowNoState: - applyGeometry(m_normalGeometry); - break; - case Qt::WindowMaximized: - case Qt::WindowFullScreen: { + if (state & Qt::WindowMinimized) { + applyGeometry(QRect()); + } else if (state & (Qt::WindowFullScreen | Qt::WindowMaximized)) { // When an application is in split-view mode, the UIScreen still has the // same geometry, but the UIWindow is resized to the area reserved for the // application. We use this to constrain the geometry used when applying the @@ -268,15 +265,8 @@ void QIOSWindow::setWindowState(Qt::WindowState state) applyGeometry(fullscreenGeometry); else applyGeometry(maximizedGeometry); - break; - } - case Qt::WindowMinimized: - applyGeometry(QRect()); - break; - case Qt::WindowActive: - Q_UNREACHABLE(); - default: - Q_UNREACHABLE(); + } else { + applyGeometry(m_normalGeometry); } } diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index ed8af8e290..42e57d0f4f 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -49,7 +49,6 @@ #include "qiosmenu.h" #endif -#include <QtCore/qoperatingsystemversion.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qwindow_p.h> #include <qpa/qwindowsysteminterface_p.h> @@ -81,10 +80,11 @@ - (id)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window { - if (self = [self initWithFrame:window->geometry().toCGRect()]) + if (self = [self initWithFrame:window->geometry().toCGRect()]) { m_qioswindow = window; + m_accessibleElements = [[NSMutableArray alloc] init]; + } - m_accessibleElements = [[NSMutableArray alloc] init]; return self; } @@ -105,7 +105,7 @@ self.multipleTouchEnabled = YES; #endif - if (QIOSIntegration::instance()->debugWindowManagement()) { + if (qEnvironmentVariableIntValue("QT_IOS_DEBUG_WINDOW_MANAGEMENT")) { static CGFloat hue = 0.0; CGFloat lastHue = hue; for (CGFloat diff = 0; diff < 0.1 || diff > 0.9; diff = fabs(hue - lastHue)) @@ -114,7 +114,6 @@ #define colorWithBrightness(br) \ [UIColor colorWithHue:hue saturation:0.5 brightness:br alpha:1.0].CGColor - self.layer.backgroundColor = colorWithBrightness(0.5); self.layer.borderColor = colorWithBrightness(1.0); self.layer.borderWidth = 1.0; } @@ -139,6 +138,13 @@ return self; } +- (void)dealloc +{ + [m_accessibleElements release]; + + [super dealloc]; +} + - (void)willMoveToWindow:(UIWindow *)newWindow { // UIKIt will normally set the scale factor of a view to match the corresponding @@ -186,24 +192,13 @@ qWarning() << m_qioswindow->window() << "is backed by a UIView that has a transform set. This is not supported."; - // The original geometry requested by setGeometry() might be different - // from what we end up with after applying window constraints. - QRect requestedGeometry = m_qioswindow->geometry(); - - QRect actualGeometry = QRectF::fromCGRect(self.frame).toRect(); - - // Persist the actual/new geometry so that QWindow::geometry() can - // be queried on the resize event. - m_qioswindow->QPlatformWindow::setGeometry(actualGeometry); - - QRect previousGeometry = requestedGeometry != actualGeometry ? - requestedGeometry : qt_window_private(m_qioswindow->window())->geometry; - QWindow *window = m_qioswindow->window(); - qCDebug(lcQpaWindow) << m_qioswindow->window() << "new geometry is" << actualGeometry; - QWindowSystemInterface::handleGeometryChange(window, actualGeometry, previousGeometry); + QRect lastReportedGeometry = qt_window_private(window)->geometry; + QRect currentGeometry = QRectF::fromCGRect(self.frame).toRect(); + qCDebug(lcQpaWindow) << m_qioswindow->window() << "new geometry is" << currentGeometry; + QWindowSystemInterface::handleGeometryChange(window, currentGeometry); - if (actualGeometry.size() != previousGeometry.size()) { + if (currentGeometry.size() != lastReportedGeometry.size()) { // Trigger expose event on resize [self setNeedsDisplay]; @@ -339,7 +334,7 @@ QTouchDevice *touchDevice = QIOSIntegration::instance()->touchDevice(); QTouchDevice::Capabilities touchCapabilities = touchDevice->capabilities(); - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::IOS, 9)) { + if (__builtin_available(iOS 9, *)) { if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) touchCapabilities |= QTouchDevice::Pressure; else diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm index 8dafaea552..69a4d375bd 100644 --- a/src/plugins/platforms/ios/quiview_accessibility.mm +++ b/src/plugins/platforms/ios/quiview_accessibility.mm @@ -50,7 +50,7 @@ return; QAccessible::Id accessibleId = QAccessible::uniqueId(iface); UIAccessibilityElement *elem = [[QMacAccessibilityElement alloc] initWithId: accessibleId withAccessibilityContainer: self]; - [m_accessibleElements addObject: elem]; + [m_accessibleElements addObject:[elem autorelease]]; } - (void)createAccessibleContainer:(QAccessibleInterface *)iface diff --git a/src/plugins/platforms/mirclient/qmirclientwindow.cpp b/src/plugins/platforms/mirclient/qmirclientwindow.cpp index adc8ae652a..decd21516e 100644 --- a/src/plugins/platforms/mirclient/qmirclientwindow.cpp +++ b/src/plugins/platforms/mirclient/qmirclientwindow.cpp @@ -516,7 +516,6 @@ UbuntuSurface::UbuntuSurface(QMirClientWindow *platformWindow, EGLDisplay displa // Assume that the buffer size matches the surface size at creation time mBufferSize = geom.size(); - platformWindow->QPlatformWindow::setGeometry(geom); QWindowSystemInterface::handleGeometryChange(mWindow, geom); qCDebug(mirclient) << "Created surface with geometry:" << geom << "title:" << mWindow->title() @@ -636,7 +635,6 @@ void UbuntuSurface::onSwapBuffersDone() QRect newGeometry = mPlatformWindow->geometry(); newGeometry.setSize(mBufferSize); - mPlatformWindow->QPlatformWindow::setGeometry(newGeometry); QWindowSystemInterface::handleGeometryChange(mWindow, newGeometry); } else { qCDebug(mirclientBufferSwap, "onSwapBuffersDone(window=%p) [%d] - buffer size (%d,%d)", @@ -785,8 +783,16 @@ void QMirClientWindow::handleSurfaceStateChanged(Qt::WindowState state) QWindowSystemInterface::handleWindowStateChanged(window(), state); } -void QMirClientWindow::setWindowState(Qt::WindowState state) +void QMirClientWindow::setWindowState(Qt::WindowStates states) { + Qt::WindowState state = Qt::WindowNoState; + if (states & Qt::WindowMinimized) + state = Qt::WindowMinimized; + else if (states & Qt::WindowFullScreen) + state = Qt::WindowFullScreen; + else if (states & Qt::WindowMaximized) + state = Qt::WindowMaximized; + QMutexLocker lock(&mMutex); qCDebug(mirclient, "setWindowState(window=%p, %s)", this, qtWindowStateToStr(state)); diff --git a/src/plugins/platforms/mirclient/qmirclientwindow.h b/src/plugins/platforms/mirclient/qmirclientwindow.h index 324e7691ff..6c5695d62f 100644 --- a/src/plugins/platforms/mirclient/qmirclientwindow.h +++ b/src/plugins/platforms/mirclient/qmirclientwindow.h @@ -73,7 +73,7 @@ public: WId winId() const override; QRect geometry() const override; void setGeometry(const QRect&) override; - void setWindowState(Qt::WindowState state) override; + void setWindowState(Qt::WindowStates state) override; void setWindowFlags(Qt::WindowFlags flags) override; void setVisible(bool visible) override; void setWindowTitle(const QString &title) override; diff --git a/src/plugins/platforms/offscreen/qoffscreencommon.h b/src/plugins/platforms/offscreen/qoffscreencommon.h index 72d6f16d26..1a9d65972d 100644 --- a/src/plugins/platforms/offscreen/qoffscreencommon.h +++ b/src/plugins/platforms/offscreen/qoffscreencommon.h @@ -75,7 +75,6 @@ public: class QOffscreenDrag : public QPlatformDrag { public: - QMimeData *platformDropData() Q_DECL_OVERRIDE { return 0; } Qt::DropAction drag(QDrag *) Q_DECL_OVERRIDE { return Qt::IgnoreAction; } }; #endif diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration.cpp b/src/plugins/platforms/offscreen/qoffscreenintegration.cpp index 0c39950019..75bb786b28 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration.cpp +++ b/src/plugins/platforms/offscreen/qoffscreenintegration.cpp @@ -59,6 +59,9 @@ #include <QtGui/private/qpixmap_raster_p.h> #include <QtGui/private/qguiapplication_p.h> +#include <qpa/qplatforminputcontextfactory_p.h> +#include <qpa/qplatforminputcontext.h> +#include <qpa/qplatformtheme.h> #include <qpa/qplatformservices.h> @@ -118,6 +121,16 @@ QOffscreenIntegration::~QOffscreenIntegration() { } +void QOffscreenIntegration::initialize() +{ + m_inputContext.reset(QPlatformInputContextFactory::create()); +} + +QPlatformInputContext *QOffscreenIntegration::inputContext() const +{ + return m_inputContext.data(); +} + bool QOffscreenIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { @@ -155,6 +168,37 @@ QAbstractEventDispatcher *QOffscreenIntegration::createEventDispatcher() const #endif } +static QString themeName() { return QStringLiteral("offscreen"); } + +QStringList QOffscreenIntegration::themeNames() const +{ + return QStringList(themeName()); +} + +// Restrict the styles to "fusion" to prevent native styles requiring native +// window handles (eg Windows Vista style) from being used. +class OffscreenTheme : public QPlatformTheme +{ +public: + OffscreenTheme() {} + + QVariant themeHint(ThemeHint h) const override + { + switch (h) { + case StyleNames: + return QVariant(QStringList(QStringLiteral("fusion"))); + default: + break; + } + return QPlatformTheme::themeHint(h); + } +}; + +QPlatformTheme *QOffscreenIntegration::createPlatformTheme(const QString &name) const +{ + return name == themeName() ? new OffscreenTheme() : nullptr; +} + QPlatformFontDatabase *QOffscreenIntegration::fontDatabase() const { return m_fontDatabase.data(); diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration.h b/src/plugins/platforms/offscreen/qoffscreenintegration.h index df647a31ef..f72587d11a 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration.h +++ b/src/plugins/platforms/offscreen/qoffscreenintegration.h @@ -54,6 +54,7 @@ public: QOffscreenIntegration(); ~QOffscreenIntegration(); + void initialize() Q_DECL_OVERRIDE; bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; @@ -61,11 +62,16 @@ public: #ifndef QT_NO_DRAGANDDROP QPlatformDrag *drag() const Q_DECL_OVERRIDE; #endif + + QPlatformInputContext *inputContext() const Q_DECL_OVERRIDE; QPlatformServices *services() const Q_DECL_OVERRIDE; QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QStringList themeNames() const; + QPlatformTheme *createPlatformTheme(const QString &name) const; + static QOffscreenIntegration *createOffscreenIntegration(); private: @@ -73,6 +79,7 @@ private: #ifndef QT_NO_DRAGANDDROP QScopedPointer<QPlatformDrag> m_drag; #endif + QScopedPointer<QPlatformInputContext> m_inputContext; QScopedPointer<QPlatformServices> m_services; }; diff --git a/src/plugins/platforms/offscreen/qoffscreenwindow.cpp b/src/plugins/platforms/offscreen/qoffscreenwindow.cpp index 81f262f9ed..832e94034d 100644 --- a/src/plugins/platforms/offscreen/qoffscreenwindow.cpp +++ b/src/plugins/platforms/offscreen/qoffscreenwindow.cpp @@ -56,7 +56,7 @@ QOffscreenWindow::QOffscreenWindow(QWindow *window) if (window->windowState() == Qt::WindowNoState) setGeometry(window->geometry()); else - setWindowState(window->windowState()); + setWindowState(window->windowStates()); QWindowSystemInterface::flushWindowSystemEvents(); @@ -166,26 +166,19 @@ void QOffscreenWindow::setFrameMarginsEnabled(bool enabled) } } -void QOffscreenWindow::setWindowState(Qt::WindowState state) +void QOffscreenWindow::setWindowState(Qt::WindowStates state) { - setFrameMarginsEnabled(state != Qt::WindowFullScreen); + setFrameMarginsEnabled(!(state & Qt::WindowFullScreen)); m_positionIncludesFrame = false; - switch (state) { - case Qt::WindowFullScreen: + if (state & Qt::WindowMinimized) + ; // nothing to do + else if (state & Qt::WindowFullScreen) setGeometryImpl(screen()->geometry()); - break; - case Qt::WindowMaximized: + else if (state & Qt::WindowMaximized) setGeometryImpl(screen()->availableGeometry().adjusted(m_margins.left(), m_margins.top(), -m_margins.right(), -m_margins.bottom())); - break; - case Qt::WindowMinimized: - break; - case Qt::WindowNoState: + else setGeometryImpl(m_normalGeometry); - break; - default: - break; - } QWindowSystemInterface::handleWindowStateChanged(window(), state); } diff --git a/src/plugins/platforms/offscreen/qoffscreenwindow.h b/src/plugins/platforms/offscreen/qoffscreenwindow.h index f75458eb8e..0dced9680a 100644 --- a/src/plugins/platforms/offscreen/qoffscreenwindow.h +++ b/src/plugins/platforms/offscreen/qoffscreenwindow.h @@ -54,7 +54,7 @@ public: ~QOffscreenWindow(); void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; + void setWindowState(Qt::WindowStates states) Q_DECL_OVERRIDE; QMargins frameMargins() const Q_DECL_OVERRIDE; diff --git a/src/plugins/platforms/qnx/qqnxwindow.cpp b/src/plugins/platforms/qnx/qqnxwindow.cpp index 30288ccb20..2253e3b23d 100644 --- a/src/plugins/platforms/qnx/qqnxwindow.cpp +++ b/src/plugins/platforms/qnx/qqnxwindow.cpp @@ -586,7 +586,7 @@ void QQnxWindow::setFocus(screen_window_t newFocusWindow) } } -void QQnxWindow::setWindowState(Qt::WindowState state) +void QQnxWindow::setWindowState(Qt::WindowStates state) { qWindowDebug() << "state =" << state; @@ -749,32 +749,19 @@ void QQnxWindow::updateZorder(screen_window_t window, int &topZorder) void QQnxWindow::applyWindowState() { - switch (m_windowState) { - - // WindowActive is not an accepted parameter according to the docs - case Qt::WindowActive: - return; - - case Qt::WindowMinimized: + if (m_windowState & Qt::WindowMinimized) { minimize(); if (m_unmaximizedGeometry.isValid()) setGeometry(m_unmaximizedGeometry); else setGeometry(m_screen->geometry()); - - break; - - case Qt::WindowMaximized: - case Qt::WindowFullScreen: + } else if (m_windowState & (Qt::WindowMaximized | Qt::WindowFullScreen)) { m_unmaximizedGeometry = geometry(); - setGeometry(m_windowState == Qt::WindowMaximized ? m_screen->availableGeometry() : m_screen->geometry()); - break; - - case Qt::WindowNoState: - if (m_unmaximizedGeometry.isValid()) - setGeometry(m_unmaximizedGeometry); - break; + setGeometry(m_windowState & Qt::WindowFullScreen ? m_screen->geometry() + : m_screen->availableGeometry()); + } else if (m_unmaximizedGeometry.isValid()) { + setGeometry(m_unmaximizedGeometry); } } diff --git a/src/plugins/platforms/qnx/qqnxwindow.h b/src/plugins/platforms/qnx/qqnxwindow.h index e248e04462..f96edc49e4 100644 --- a/src/plugins/platforms/qnx/qqnxwindow.h +++ b/src/plugins/platforms/qnx/qqnxwindow.h @@ -85,7 +85,7 @@ public: void raise() override; void lower() override; void requestActivateWindow() override; - void setWindowState(Qt::WindowState state) override; + void setWindowState(Qt::WindowStates state) override; void setExposed(bool exposed); void propagateSizeHints() override; @@ -141,7 +141,7 @@ private: bool m_visible; bool m_exposed; QRect m_unmaximizedGeometry; - Qt::WindowState m_windowState; + Qt::WindowStates m_windowState; QString m_mmRendererWindowName; screen_window_t m_mmRendererWindow; diff --git a/src/plugins/platforms/vnc/qvncintegration.cpp b/src/plugins/platforms/vnc/qvncintegration.cpp index 8516e994f5..1e2cf6292c 100644 --- a/src/plugins/platforms/vnc/qvncintegration.cpp +++ b/src/plugins/platforms/vnc/qvncintegration.cpp @@ -52,9 +52,6 @@ #include <QtGui/private/qguiapplication_p.h> #include <qpa/qplatforminputcontextfactory_p.h> #include <private/qinputdevicemanager_p_p.h> -#if QT_CONFIG(libinput) -#include <QtInputSupport/private/qlibinputhandler_p.h> -#endif #include <QtCore/QRegularExpression> diff --git a/src/plugins/platforms/windows/accessible/accessible.pri b/src/plugins/platforms/windows/accessible/accessible.pri index 0e3aacc558..557bdfe307 100644 --- a/src/plugins/platforms/windows/accessible/accessible.pri +++ b/src/plugins/platforms/windows/accessible/accessible.pri @@ -6,13 +6,14 @@ HEADERS += \ $$PWD/qwindowsaccessibility.h \ $$PWD/comutils.h -SOURCES += $$PWD/qwindowsmsaaaccessible.cpp -HEADERS += $$PWD/qwindowsmsaaaccessible.h +SOURCES += \ + $$PWD/qwindowsmsaaaccessible.cpp \ + $$PWD/iaccessible2.cpp + +HEADERS += \ + $$PWD/qwindowsmsaaaccessible.h \ + $$PWD/iaccessible2.h -!mingw: { - SOURCES += $$PWD/iaccessible2.cpp - HEADERS += $$PWD/iaccessible2.h - include(../../../../3rdparty/iaccessible2/iaccessible2.pri) -} +include(../../../../3rdparty/iaccessible2/iaccessible2.pri) mingw: LIBS *= -luuid diff --git a/src/plugins/platforms/windows/accessible/iaccessible2.cpp b/src/plugins/platforms/windows/accessible/iaccessible2.cpp index f9e8231843..75af65b5ab 100644 --- a/src/plugins/platforms/windows/accessible/iaccessible2.cpp +++ b/src/plugins/platforms/windows/accessible/iaccessible2.cpp @@ -36,7 +36,7 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include <QtCore/QtConfig> +#include <QtCore/qglobal.h> #ifndef QT_NO_ACCESSIBILITY #include "iaccessible2.h" @@ -61,40 +61,7 @@ static inline T *coTaskMemAllocArray(int size) /**************************************************************\ * AccessibleApplication * **************************************************************/ -// IUnknown -HRESULT STDMETHODCALLTYPE AccessibleApplication::QueryInterface(REFIID id, LPVOID *iface) -{ - *iface = 0; - if (id == IID_IUnknown) { - qCDebug(lcQpaAccessibility) << "AccessibleApplication::QI(): IID_IUnknown"; - *iface = static_cast<IUnknown *>(this); - } else if (id == IID_IAccessibleApplication) { - qCDebug(lcQpaAccessibility) << "AccessibleApplication::QI(): IID_IAccessibleApplication"; - *iface = static_cast<IAccessibleApplication*>(this); - } - - if (*iface) { - AddRef(); - return S_OK; - } - return E_NOINTERFACE; -} - -ULONG STDMETHODCALLTYPE AccessibleApplication::AddRef() -{ - return ++m_ref; -} -ULONG STDMETHODCALLTYPE AccessibleApplication::Release() -{ - if (!--m_ref) { - delete this; - return 0; - } - return m_ref; -} - -/* IAccessibleApplication */ HRESULT STDMETHODCALLTYPE AccessibleApplication::get_appName(/* [retval][out] */ BSTR *name) { const QString appName = QGuiApplication::applicationName(); @@ -127,40 +94,11 @@ HRESULT STDMETHODCALLTYPE AccessibleApplication::get_toolkitVersion(/* [retval][ **************************************************************/ AccessibleRelation::AccessibleRelation(const QList<QAccessibleInterface *> &targets, QAccessible::Relation relation) - : m_targets(targets), m_relation(relation), m_ref(1) + : m_targets(targets), m_relation(relation) { Q_ASSERT(m_targets.count()); } -/* IUnknown */ -HRESULT STDMETHODCALLTYPE AccessibleRelation::QueryInterface(REFIID id, LPVOID *iface) -{ - *iface = 0; - if (id == IID_IUnknown || id == IID_IAccessibleRelation) - *iface = static_cast<IUnknown *>(this); - - if (*iface) { - AddRef(); - return S_OK; - } - - return E_NOINTERFACE; -} - -ULONG STDMETHODCALLTYPE AccessibleRelation::AddRef() -{ - return ++m_ref; -} - -ULONG STDMETHODCALLTYPE AccessibleRelation::Release() -{ - if (!--m_ref) { - delete this; - return 0; - } - return m_ref; -} - /* IAccessibleRelation */ HRESULT STDMETHODCALLTYPE AccessibleRelation::get_relationType( /* [retval][out] */ BSTR *relationType) @@ -237,56 +175,53 @@ HRESULT STDMETHODCALLTYPE AccessibleRelation::get_targets( **************************************************************/ HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::QueryInterface(REFIID id, LPVOID *iface) { + *iface = nullptr; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return E_NOINTERFACE; - HRESULT hr = QWindowsMsaaAccessible::QueryInterface(id, iface); - if (!SUCCEEDED(hr)) { - if (id == IID_IServiceProvider) { - *iface = static_cast<IServiceProvider *>(this); - } else if (id == IID_IAccessible2) { - *iface = static_cast<IAccessible2 *>(this); - } else if (id == IID_IAccessibleAction) { - if (accessible->actionInterface()) - *iface = static_cast<IAccessibleAction *>(this); - } else if (id == IID_IAccessibleComponent) { - *iface = static_cast<IAccessibleComponent *>(this); - } else if (id == IID_IAccessibleEditableText) { - if (accessible->editableTextInterface() || - accessible->role() == QAccessible::EditableText) - { - *iface = static_cast<IAccessibleEditableText *>(this); - } - } else if (id == IID_IAccessibleHyperlink) { - //*iface = static_cast<IAccessibleHyperlink *>(this); - } else if (id == IID_IAccessibleHypertext) { - //*iface = static_cast<IAccessibleHypertext *>(this); - } else if (id == IID_IAccessibleImage) { - //*iface = static_cast<IAccessibleImage *>(this); - } else if (id == IID_IAccessibleTable) { - //*iface = static_cast<IAccessibleTable *>(this); // not supported - } else if (id == IID_IAccessibleTable2) { - if (accessible->tableInterface()) - *iface = static_cast<IAccessibleTable2 *>(this); - } else if (id == IID_IAccessibleTableCell) { - if (accessible->tableCellInterface()) - *iface = static_cast<IAccessibleTableCell *>(this); - } else if (id == IID_IAccessibleText) { - if (accessible->textInterface()) - *iface = static_cast<IAccessibleText *>(this); - } else if (id == IID_IAccessibleValue) { - if (accessible->valueInterface()) - *iface = static_cast<IAccessibleValue *>(this); - } - if (*iface) { - AddRef(); - hr = S_OK; - } else { - hr = E_NOINTERFACE; + if (SUCCEEDED(QWindowsMsaaAccessible::QueryInterface(id, iface)) + || qWindowsComQueryInterface<IServiceProvider>(this, id, iface) + || qWindowsComQueryInterface<IAccessible2>(this, id, iface) + || qWindowsComQueryInterface<IAccessibleComponent>(this, id, iface)) { + return S_OK; + } + + if (id == IID_IAccessibleAction) { + if (accessible->actionInterface()) + *iface = static_cast<IAccessibleAction *>(this); + } else if (id == IID_IAccessibleEditableText) { + if (accessible->editableTextInterface() || + accessible->role() == QAccessible::EditableText) + { + *iface = static_cast<IAccessibleEditableText *>(this); } + } else if (id == IID_IAccessibleHyperlink) { + //*iface = static_cast<IAccessibleHyperlink *>(this); + } else if (id == IID_IAccessibleHypertext) { + //*iface = static_cast<IAccessibleHypertext *>(this); + } else if (id == IID_IAccessibleImage) { + //*iface = static_cast<IAccessibleImage *>(this); + } else if (id == IID_IAccessibleTable) { + //*iface = static_cast<IAccessibleTable *>(this); // not supported + } else if (id == IID_IAccessibleTable2) { + if (accessible->tableInterface()) + *iface = static_cast<IAccessibleTable2 *>(this); + } else if (id == IID_IAccessibleTableCell) { + if (accessible->tableCellInterface()) + *iface = static_cast<IAccessibleTableCell *>(this); + } else if (id == IID_IAccessibleText) { + if (accessible->textInterface()) + *iface = static_cast<IAccessibleText *>(this); + } else if (id == IID_IAccessibleValue) { + if (accessible->valueInterface()) + *iface = static_cast<IAccessibleValue *>(this); } - return hr; + if (*iface) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; } @@ -1593,7 +1528,7 @@ HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::QueryService(REFGUID guidServic return E_POINTER; Q_UNUSED(guidService); *iface = 0; - qCDebug(lcQpaAccessibility) << "QWindowsIA2Accessible::QS(): " << IIDToString(riid); + qCDebug(lcQpaAccessibility) << "QWindowsIA2Accessible::QS(): " << QWindowsAccessibleGuid(riid); if (guidService == IID_IAccessible) { @@ -1692,45 +1627,6 @@ HRESULT QWindowsIA2Accessible::wrapListOfCells(const QList<QAccessibleInterface* return count > 0 ? S_OK : S_FALSE; } -#define IF_EQUAL_RETURN_IIDSTRING(id, iid) if (id == iid) return QByteArray(#iid) - -QByteArray QWindowsIA2Accessible::IIDToString(REFIID id) -{ - QByteArray strGuid = QWindowsMsaaAccessible::IIDToString(id); - if (!strGuid.isEmpty()) - return strGuid; - - IF_EQUAL_RETURN_IIDSTRING(id, IID_IUnknown); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IDispatch); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessible); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IOleWindow); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IServiceProvider); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessible2); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleAction); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleApplication); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleComponent); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleEditableText); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleHyperlink); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleHypertext); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleImage); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleRelation); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleTable); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleTable2); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleTableCell); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleText); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessibleValue); - - // else... -#if 0 // Can be useful for debugging, but normally we'd like to reduce the noise a bit... - OLECHAR szGuid[39]={0}; - ::StringFromGUID2(id, szGuid, 39); - strGuid.reserve(40); - ::WideCharToMultiByte(CP_UTF8, 0, szGuid, 39, strGuid.data(), 39, NULL, NULL); - strGuid[38] = '\0'; -#endif - return strGuid; -} - // Q_STATIC_ASSERT(IA2_ROLE_CANVAS == QAccessible::Canvas); // ### Qt 6: make them the same Q_STATIC_ASSERT(IA2_ROLE_COLOR_CHOOSER == static_cast<IA2Role>(QAccessible::ColorChooser)); Q_STATIC_ASSERT(IA2_ROLE_FOOTER == static_cast<IA2Role>(QAccessible::Footer)); diff --git a/src/plugins/platforms/windows/accessible/iaccessible2.h b/src/plugins/platforms/windows/accessible/iaccessible2.h index bc5f5be60f..d987016e15 100644 --- a/src/plugins/platforms/windows/accessible/iaccessible2.h +++ b/src/plugins/platforms/windows/accessible/iaccessible2.h @@ -39,9 +39,10 @@ #ifndef IACCESSIBLE2_H #define IACCESSIBLE2_H -#include <QtCore/QtConfig> +#include <QtCore/qglobal.h> #ifndef QT_NO_ACCESSIBILITY +#include "qwindowscombase.h" #include "qwindowsmsaaaccessible.h" #include "comutils.h" @@ -51,6 +52,13 @@ QT_BEGIN_NAMESPACE +#ifdef Q_CC_MINGW +QT_WARNING_DISABLE_GCC("-Wunused-function") // MinGW 7.X claims it is unused +// MinGW's __uuidof operator does not work for the Accessible2 interfaces +template <> +IID qUuidOf<IAccessibleComponent>() { return IID_IAccessibleComponent; } +#endif // Q_CC_MINGW + class QWindowsIA2Accessible : public QWindowsMsaaAccessible, public IAccessibleAction, public IAccessibleComponent, @@ -249,7 +257,6 @@ private: HRESULT getRelationsHelper(IAccessibleRelation **relations, int startIndex, long maxRelations, long *nRelations = 0); HRESULT wrapListOfCells(const QList<QAccessibleInterface*> &inputCells, IUnknown ***outputAccessibles, long *nCellCount); - QByteArray IIDToString(REFIID id); QString textForRange(int startOffset, int endOffset) const; void replaceTextFallback(long startOffset, long endOffset, const QString &txt); @@ -258,28 +265,31 @@ private: /**************************************************************\ * AccessibleApplication * **************************************************************/ -class AccessibleApplication : public IAccessibleApplication + +#ifdef Q_CC_MINGW +// MinGW's __uuidof operator does not work for the IAccessible2 interfaces +template <> +IID qUuidOf<IAccessibleApplication>() { return IID_IAccessibleApplication; } + +template <> +IID qUuidOf<IAccessible2>() { return IID_IAccessible2; } + +template <> +IID qUuidOf<IAccessibleRelation>() { return IID_IAccessibleRelation; } +#endif // Q_CC_MINGW + +class AccessibleApplication : public QWindowsComBase<IAccessibleApplication> { public: - AccessibleApplication() : m_ref(1) - { - - } + AccessibleApplication() {} virtual ~AccessibleApplication() {} - /* IUnknown */ - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - /* IAccessibleApplication */ HRESULT STDMETHODCALLTYPE get_appName(/* [retval][out] */ BSTR *name); HRESULT STDMETHODCALLTYPE get_appVersion(/* [retval][out] */ BSTR *version); HRESULT STDMETHODCALLTYPE get_toolkitName(/* [retval][out] */ BSTR *name); HRESULT STDMETHODCALLTYPE get_toolkitVersion(/* [retval][out] */ BSTR *version); -private: - ULONG m_ref; }; @@ -287,7 +297,10 @@ private: /**************************************************************\ * AccessibleRelation * **************************************************************/ -class AccessibleRelation : public IAccessibleRelation + + + +class AccessibleRelation : public QWindowsComBase<IAccessibleRelation> { public: AccessibleRelation(const QList<QAccessibleInterface *> &targets, @@ -295,11 +308,6 @@ public: virtual ~AccessibleRelation() {} - /* IUnknown */ - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - /* IAccessibleRelation */ HRESULT STDMETHODCALLTYPE get_relationType(BSTR *relationType); HRESULT STDMETHODCALLTYPE get_localizedRelationType(BSTR *localizedRelationType); @@ -341,7 +349,6 @@ private: QList<QAccessibleInterface *> m_targets; QAccessible::Relation m_relation; - ULONG m_ref; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp b/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp index aed9c94003..d48cb9674f 100644 --- a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp +++ b/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include <QtCore/QtConfig> +#include <QtCore/qglobal.h> #ifndef QT_NO_ACCESSIBILITY @@ -56,11 +56,7 @@ #include <QtFontDatabaseSupport/private/qwindowsfontdatabase_p.h> // registry helper #include "qwindowsaccessibility.h" -#ifdef Q_CC_MINGW -# include "qwindowsmsaaaccessible.h" -#else -# include "iaccessible2.h" -#endif +#include "iaccessible2.h" #include "comutils.h" #include <oleacc.h> @@ -198,11 +194,7 @@ IAccessible *QWindowsAccessibility::wrap(QAccessibleInterface *acc) if (!QAccessible::uniqueId(acc)) QAccessible::registerAccessibleInterface(acc); -# ifdef Q_CC_MINGW - QWindowsMsaaAccessible *wacc = new QWindowsMsaaAccessible(acc); -# else QWindowsIA2Accessible *wacc = new QWindowsIA2Accessible(acc); -# endif IAccessible *iacc = 0; wacc->QueryInterface(IID_IAccessible, reinterpret_cast<void **>(&iacc)); return iacc; diff --git a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp b/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp index 25b1577772..fe2b9335cb 100644 --- a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp +++ b/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp @@ -37,11 +37,12 @@ ** ****************************************************************************/ -#include <QtCore/QtConfig> +#include <QtCore/qglobal.h> #ifndef QT_NO_ACCESSIBILITY #include "qwindowsmsaaaccessible.h" #include "qwindowsaccessibility.h" +#include "qwindowscombase.h" #include <oleacc.h> #include <servprov.h> #include <winuser.h> @@ -71,61 +72,22 @@ QT_BEGIN_NAMESPACE -class QWindowsEnumerate : public IEnumVARIANT +class QWindowsEnumerate : public QWindowsComBase<IEnumVARIANT> { public: - QWindowsEnumerate(const QVector<int> &a) - : ref(0), current(0),array(a) - { - } - + QWindowsEnumerate(const QVector<int> &a) : QWindowsComBase<IEnumVARIANT>(0), current(0),array(a) {} virtual ~QWindowsEnumerate() {} - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - HRESULT STDMETHODCALLTYPE Clone(IEnumVARIANT **ppEnum); HRESULT STDMETHODCALLTYPE Next(unsigned long celt, VARIANT FAR* rgVar, unsigned long FAR* pCeltFetched); HRESULT STDMETHODCALLTYPE Reset(); HRESULT STDMETHODCALLTYPE Skip(unsigned long celt); private: - ULONG ref; ULONG current; QVector<int> array; }; -HRESULT STDMETHODCALLTYPE QWindowsEnumerate::QueryInterface(REFIID id, LPVOID *iface) -{ - *iface = 0; - if (id == IID_IUnknown) - *iface = static_cast<IUnknown *>(this); - else if (id == IID_IEnumVARIANT) - *iface = static_cast<IEnumVARIANT *>(this); - - if (*iface) { - AddRef(); - return S_OK; - } - - return E_NOINTERFACE; -} - -ULONG STDMETHODCALLTYPE QWindowsEnumerate::AddRef() -{ - return ++ref; -} - -ULONG STDMETHODCALLTYPE QWindowsEnumerate::Release() -{ - if (!--ref) { - delete this; - return 0; - } - return ref; -} - HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Clone(IEnumVARIANT **ppEnum) { QWindowsEnumerate *penum = 0; @@ -193,29 +155,17 @@ void accessibleDebugClientCalls_helper(const char* funcName, const QAccessibleIn **************************************************************/ HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::QueryInterface(REFIID id, LPVOID *iface) { - *iface = 0; - - QByteArray strIID = IIDToString(id); - if (!strIID.isEmpty()) { - qCDebug(lcQpaAccessibility) << "QWindowsIA2Accessible::QI() - IID:" - << strIID << ", iface:" << accessibleInterface(); + *iface = nullptr; + const bool result = qWindowsComQueryUnknownInterfaceMulti<IAccessible2>(this, id, iface) + || qWindowsComQueryInterface<IDispatch>(this, id, iface) + || qWindowsComQueryInterface<IAccessible>(this, id, iface) + || qWindowsComQueryInterface<IOleWindow>(this, id, iface); + + if (result) { + qCDebug(lcQpaAccessibility) << "QWindowsIA2Accessible::QI() - " + << QWindowsAccessibleGuid(id) << ", iface:" << accessibleInterface(); } - if (id == IID_IUnknown) { - *iface = static_cast<IUnknown *>(static_cast<IDispatch *>(this)); - } else if (id == IID_IDispatch) { - *iface = static_cast<IDispatch *>(this); - } else if (id == IID_IAccessible) { - *iface = static_cast<IAccessible *>(this); - } else if (id == IID_IOleWindow) { - *iface = static_cast<IOleWindow *>(this); - } - - if (*iface) { - AddRef(); - return S_OK; - } - - return E_NOINTERFACE; + return result ? S_OK : E_NOINTERFACE; } ULONG STDMETHODCALLTYPE QWindowsMsaaAccessible::AddRef() @@ -1209,16 +1159,64 @@ HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::ContextSensitiveHelp(BOOL) return S_OK; } -#define IF_EQUAL_RETURN_IIDSTRING(id, iid) if (id == iid) return QByteArray(#iid) -QByteArray QWindowsMsaaAccessible::IIDToString(REFIID id) +const char *QWindowsAccessibleGuid::iidToString(const GUID &id) { - IF_EQUAL_RETURN_IIDSTRING(id, IID_IUnknown); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IDispatch); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IAccessible); - IF_EQUAL_RETURN_IIDSTRING(id, IID_IOleWindow); + const char *result = nullptr; + if (id == IID_IUnknown) + result = "IID_IUnknown"; + else if (id == IID_IDispatch) + result = "IID_IDispatch"; + else if (id == IID_IAccessible) + result = "IID_IAccessible"; + else if (id == IID_IOleWindow) + result = "IID_IOleWindow"; + else if (id == IID_IServiceProvider) + result = "IID_IServiceProvider"; + else if (id == IID_IAccessible2) + result = "IID_IAccessible2"; + else if (id == IID_IAccessibleAction) + result = "IID_IAccessibleAction"; + else if (id == IID_IAccessibleApplication) + result = "IID_IAccessibleApplication"; + else if (id == IID_IAccessibleComponent) + result = "IID_IAccessibleComponent"; + else if (id == IID_IAccessibleEditableText) + result = "IID_IAccessibleEditableText"; + else if (id == IID_IAccessibleHyperlink) + result = "IID_IAccessibleHyperlink"; + else if (id == IID_IAccessibleHypertext) + result = "IID_IAccessibleHypertext"; + else if (id == IID_IAccessibleImage) + result = "IID_IAccessibleImage"; + else if (id == IID_IAccessibleRelation) + result = "IID_IAccessibleRelation"; + else if (id == IID_IAccessibleTable) + result = "IID_IAccessibleTable"; + else if (id == IID_IAccessibleTable2) + result = "IID_IAccessibleTable2"; + else if (id == IID_IAccessibleTableCell) + result = "IID_IAccessibleTableCell"; + else if (id == IID_IAccessibleText) + result = "IID_IAccessibleText"; + else if (id == IID_IAccessibleValue) + result = "IID_IAccessibleValue"; + return result; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug, const GUID &); - return QByteArray(); +QDebug operator<<(QDebug d, const QWindowsAccessibleGuid &aguid) +{ + QDebugStateSaver saver(d); + d.nospace(); + if (const char *ids = QWindowsAccessibleGuid::iidToString(aguid.guid())) + d << ids; + else + d << aguid.guid(); + return d; } +#endif // !QT_NO_DEBUG_STREAM QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h b/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h index fd00f8ac8b..5380fc2411 100644 --- a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h +++ b/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h @@ -39,21 +39,14 @@ #ifndef QWINDOWSMSAAACCESSIBLE_H #define QWINDOWSMSAAACCESSIBLE_H -#include <QtCore/QtConfig> -#ifndef QT_NO_ACCESSIBILITY #include <QtCore/qglobal.h> +#ifndef QT_NO_ACCESSIBILITY #include <QtCore/qt_windows.h> #include <QtCore/qsharedpointer.h> #include <QtGui/qaccessible.h> -#ifndef Q_CC_MINGW -# include <oleacc.h> -# include "ia2_api_all.h" // IAccessible2 inherits from IAccessible -#else - // MinGW -# include <basetyps.h> -# include <oleacc.h> -#endif +#include <oleacc.h> +#include "ia2_api_all.h" // IAccessible2 inherits from IAccessible QT_BEGIN_NAMESPACE @@ -69,16 +62,26 @@ void accessibleDebugClientCalls_helper(const char* funcName, const QAccessibleIn QWindow *window_helper(const QAccessibleInterface *iface); +class QWindowsAccessibleGuid // Helper for QDebug, outputs known ids by name. +{ +public: + explicit QWindowsAccessibleGuid(const GUID &g) : m_guid(g) {} + GUID guid () const { return m_guid; } + static const char *iidToString(const GUID &id); + +private: + GUID m_guid; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QWindowsAccessibleGuid &aguid); +#endif + /**************************************************************\ * QWindowsAccessible * **************************************************************/ -class QWindowsMsaaAccessible : public -#ifdef Q_CC_MINGW - IAccessible -#else - IAccessible2 -#endif - , public IOleWindow + +class QWindowsMsaaAccessible : public IAccessible2, public IOleWindow { public: QWindowsMsaaAccessible(QAccessibleInterface *a) @@ -132,8 +135,6 @@ public: HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode); protected: - virtual QByteArray IIDToString(REFIID id); - QAccessible::Id id; QAccessibleInterface *accessibleInterface() const diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index d2e1309280..c8bdc1c93e 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -123,6 +123,9 @@ enum WindowsEventType // Simplify event types EndSessionApplicationEvent = ApplicationEventFlag + 5, AppCommandEvent = ApplicationEventFlag + 6, DeviceChangeEvent = ApplicationEventFlag + 7, + MenuAboutToShowEvent = ApplicationEventFlag + 8, + AcceleratorCommandEvent = ApplicationEventFlag + 9, + MenuCommandEvent = ApplicationEventFlag + 10, InputMethodStartCompositionEvent = InputMethodEventFlag + 1, InputMethodCompositionEvent = InputMethodEventFlag + 2, InputMethodEndCompositionEvent = InputMethodEventFlag + 3, @@ -274,6 +277,11 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI return QtWindows::GestureEvent; case WM_DEVICECHANGE: return QtWindows::DeviceChangeEvent; + case WM_INITMENU: + case WM_INITMENUPOPUP: + return QtWindows::MenuAboutToShowEvent; + case WM_COMMAND: + return HIWORD(wParamIn) ? QtWindows::AcceleratorCommandEvent : QtWindows::MenuCommandEvent; case WM_DPICHANGED: return QtWindows::DpiChangedEvent; case WM_ENTERSIZEMOVE: diff --git a/src/plugins/platforms/windows/qwindowscombase.h b/src/plugins/platforms/windows/qwindowscombase.h new file mode 100644 index 0000000000..5e51b6b7b7 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowscombase.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSCOMBASE_H +#define QWINDOWSCOMBASE_H + +#include <QtCore/QtGlobal> + +#include <unknwn.h> + +QT_BEGIN_NAMESPACE + +// The __uuidof operator of MinGW does not work for all interfaces (for example, +// IAccessible2). Specializations of this function can be provides to work +// around this. +template <class DesiredInterface> +static IID qUuidOf() { return __uuidof(DesiredInterface); } + +// Helper for implementing IUnknown::QueryInterface. +template <class DesiredInterface, class Derived> +bool qWindowsComQueryInterface(Derived *d, REFIID id, LPVOID *iface) +{ + if (id == qUuidOf<DesiredInterface>()) { + *iface = static_cast<DesiredInterface *>(d); + d->AddRef(); + return true; + } + return false; +} + +// Helper for implementing IUnknown::QueryInterface for IUnknown +// in the case of multiple inheritance via the first inherited class. +template <class FirstInheritedInterface, class Derived> +bool qWindowsComQueryUnknownInterfaceMulti(Derived *d, REFIID id, LPVOID *iface) +{ + if (id == __uuidof(IUnknown)) { + *iface = static_cast<FirstInheritedInterface *>(d); + d->AddRef(); + return true; + } + return false; +} + +// Helper base class to provide IUnknown methods for COM classes (single inheritance) +template <class ComInterface> class QWindowsComBase : public ComInterface +{ + Q_DISABLE_COPY(QWindowsComBase) +public: + explicit QWindowsComBase(ULONG initialRefCount = 1) : m_ref(initialRefCount) {} + virtual ~QWindowsComBase() {} + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) + { + *iface = nullptr; + return qWindowsComQueryInterface<IUnknown>(this, id, iface) || qWindowsComQueryInterface<ComInterface>(this, id, iface) + ? S_OK : E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref; } + + ULONG STDMETHODCALLTYPE Release() + { + if (!--m_ref) { + delete this; + return 0; + } + return m_ref; + } + +private: + ULONG m_ref; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSCOMBASE_H diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index cda6c99ad0..9912e03cb9 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -44,6 +44,7 @@ #include "qwindowskeymapper.h" #include "qwindowsmousehandler.h" #include "qtwindowsglobal.h" +#include "qwindowsmenu.h" #include "qwindowsmime.h" #include "qwindowsinputcontext.h" #if QT_CONFIG(tabletevent) @@ -94,8 +95,10 @@ Q_LOGGING_CATEGORY(lcQpaGl, "qt.qpa.gl") Q_LOGGING_CATEGORY(lcQpaMime, "qt.qpa.mime") Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods") Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs") +Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus") Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility") +Q_LOGGING_CATEGORY(lcQpaTrayIcon, "qt.qpa.trayicon") int QWindowsContext::verbose = 0; @@ -126,11 +129,19 @@ static inline bool useRTL_Extensions() } #if QT_CONFIG(sessionmanager) -static inline QWindowsSessionManager *platformSessionManager() { +static inline QWindowsSessionManager *platformSessionManager() +{ QGuiApplicationPrivate *guiPrivate = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp)); QSessionManagerPrivate *managerPrivate = static_cast<QSessionManagerPrivate*>(QObjectPrivate::get(guiPrivate->session_manager)); return static_cast<QWindowsSessionManager *>(managerPrivate->platformSessionManager); } + +static inline bool sessionManagerInteractionBlocked() +{ + return platformSessionManager()->isInteractionBlocked(); +} +#else // QT_CONFIG(sessionmanager) +static inline bool sessionManagerInteractionBlocked() { return false; } #endif static inline int windowDpiAwareness(HWND hwnd) @@ -397,9 +408,11 @@ QList<int> QWindowsContext::possibleKeys(const QKeyEvent *e) const return d->m_keyMapper.possibleKeys(e); } -void QWindowsContext::setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx) +QSharedPointer<QWindowCreationContext> QWindowsContext::setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx) { + const QSharedPointer<QWindowCreationContext> old = d->m_creationContext; d->m_creationContext = ctx; + return old; } QSharedPointer<QWindowCreationContext> QWindowsContext::windowCreationContext() const @@ -597,6 +610,15 @@ void QWindowsContext::removeWindow(HWND hwnd) } } +QWindowsWindow *QWindowsContext::findPlatformWindow(const QWindowsMenuBar *mb) const +{ + for (auto it = d->m_windows.cbegin(), end = d->m_windows.cend(); it != end; ++it) { + if ((*it)->menuBar() == mb) + return *it; + } + return nullptr; +} + QWindowsWindow *QWindowsContext::findPlatformWindow(HWND hwnd) const { return d->m_windows.value(hwnd); @@ -934,11 +956,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, switch (et) { case QtWindows::GestureEvent: -#if QT_CONFIG(sessionmanager) - return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); -#else - return d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); -#endif + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::InputMethodOpenCandidateWindowEvent: case QtWindows::InputMethodCloseCandidateWindowEvent: // TODO: Release/regrab mouse if a popup has mouse grab. @@ -1027,11 +1045,23 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::InputMethodKeyEvent: case QtWindows::InputMethodKeyDownEvent: case QtWindows::AppCommandEvent: -#if QT_CONFIG(sessionmanager) - return platformSessionManager()->isInteractionBlocked() ? true : d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result); -#else - return d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result); -#endif + return sessionManagerInteractionBlocked() || d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result); + case QtWindows::MenuAboutToShowEvent: + if (sessionManagerInteractionBlocked()) + return true; + if (QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(wParam))) + return true; + if (platformWindow == nullptr || platformWindow->menuBar() == nullptr) + return false; + return platformWindow->menuBar()->notifyAboutToShow(reinterpret_cast<HMENU>(wParam)); + case QtWindows::MenuCommandEvent: + if (sessionManagerInteractionBlocked()) + return true; + if (QWindowsPopupMenu::notifyTriggered(LOWORD(wParam))) + return true; + if (platformWindow == nullptr || platformWindow->menuBar() == nullptr) + return false; + return platformWindow->menuBar()->notifyTriggered(LOWORD(wParam)); case QtWindows::MoveEvent: platformWindow->handleMoved(); return true; @@ -1051,11 +1081,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, return platformWindow->handleWmPaint(hwnd, message, wParam, lParam); case QtWindows::NonClientMouseEvent: if (platformWindow->frameStrutEventsEnabled()) -#if QT_CONFIG(sessionmanager) - return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); -#else - return d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); -#endif + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); break; case QtWindows::EnterSizeMoveEvent: platformWindow->setFlag(QWindowsWindow::ResizeMoveActive); @@ -1065,11 +1091,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, platformWindow->checkForScreenChanged(); return true; case QtWindows::ScrollEvent: -#if QT_CONFIG(sessionmanager) - return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); -#else - return d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); -#endif + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); case QtWindows::MouseWheelEvent: case QtWindows::MouseEvent: case QtWindows::LeaveEvent: @@ -1079,18 +1101,10 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, window = window->parent(); if (!window) return false; -#if QT_CONFIG(sessionmanager) - return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result); -#else - return d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result); -#endif + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result); } case QtWindows::TouchEvent: -#if QT_CONFIG(sessionmanager) - return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); -#else - return d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); -#endif + return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow(). case QtWindows::FocusOutEvent: handleFocusEvent(et, platformWindow); @@ -1292,6 +1306,29 @@ QTouchDevice *QWindowsContext::touchDevice() const return d->m_mouseHandler.touchDevice(); } +static DWORD readDwordRegistrySetting(const wchar_t *regKey, const wchar_t *subKey, DWORD defaultValue) +{ + DWORD result = defaultValue; + HKEY handle; + if (RegOpenKeyEx(HKEY_CURRENT_USER, regKey, 0, KEY_READ, &handle) == ERROR_SUCCESS) { + DWORD type; + if (RegQueryValueEx(handle, subKey, 0, &type, 0, 0) == ERROR_SUCCESS && type == REG_DWORD) { + DWORD value; + DWORD size = sizeof(result); + if (RegQueryValueEx(handle, subKey, 0, 0, reinterpret_cast<unsigned char *>(&value), &size) == ERROR_SUCCESS) + result = value; + } + RegCloseKey(handle); + } + return result; +} + +DWORD QWindowsContext::readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue) +{ + return readDwordRegistrySetting(L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced", + subKey, defaultValue); +} + static inline bool isEmptyRect(const RECT &rect) { return rect.right - rect.left == 0 && rect.bottom - rect.top == 0; diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index b50010321b..5c39b6068b 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -63,11 +63,14 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaGl) Q_DECLARE_LOGGING_CATEGORY(lcQpaMime) Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods) Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs) +Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus) Q_DECLARE_LOGGING_CATEGORY(lcQpaTablet) Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility) +Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon) class QWindow; class QPlatformScreen; +class QWindowsMenuBar; class QWindowsScreenManager; class QWindowsTabletSupport; class QWindowsWindow; @@ -177,6 +180,7 @@ public: QWindowsWindow *findClosestPlatformWindow(HWND) const; QWindowsWindow *findPlatformWindow(HWND) const; + QWindowsWindow *findPlatformWindow(const QWindowsMenuBar *mb) const; QWindow *findWindow(HWND) const; QWindowsWindow *findPlatformWindowAt(HWND parent, const QPoint &screenPoint, unsigned cwex_flags) const; @@ -192,7 +196,7 @@ public: QWindow *keyGrabber() const; void setKeyGrabber(QWindow *hwnd); - void setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx); + QSharedPointer<QWindowCreationContext> setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx); QSharedPointer<QWindowCreationContext> windowCreationContext() const; void setTabletAbsoluteRange(int a); @@ -216,6 +220,8 @@ public: bool asyncExpose() const; void setAsyncExpose(bool value); + static DWORD readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue); + QTouchDevice *touchDevice() const; private: diff --git a/src/plugins/platforms/windows/qwindowscursor.cpp b/src/plugins/platforms/windows/qwindowscursor.cpp index 0a09b87ba3..1da7de7451 100644 --- a/src/plugins/platforms/windows/qwindowscursor.cpp +++ b/src/plugins/platforms/windows/qwindowscursor.cpp @@ -548,6 +548,8 @@ CursorHandlePtr QWindowsCursor::standardWindowCursor(Qt::CursorShape shape) return it != m_standardCursorCache.end() ? it.value() : CursorHandlePtr(new CursorHandle); } +HCURSOR QWindowsCursor::m_overriddenCursor = nullptr; + /*! \brief Return cached pixmap cursor or create new one. */ @@ -586,6 +588,13 @@ QWindowsCursor::QWindowsCursor(const QPlatformScreen *screen) Q_UNUSED(dummy) } +inline CursorHandlePtr QWindowsCursor::cursorHandle(const QCursor &cursor) +{ + return cursor.shape() == Qt::BitmapCursor + ? pixmapWindowCursor(cursor) + : standardWindowCursor(cursor.shape()); +} + /*! \brief Set a cursor on a window. @@ -603,9 +612,7 @@ void QWindowsCursor::changeCursor(QCursor *cursorIn, QWindow *window) platformWindow->setCursor(CursorHandlePtr(new CursorHandle)); return; } - const CursorHandlePtr wcursor = - cursorIn->shape() == Qt::BitmapCursor ? - pixmapWindowCursor(*cursorIn) : standardWindowCursor(cursorIn->shape()); + const CursorHandlePtr wcursor = cursorHandle(*cursorIn); if (wcursor->handle()) { platformWindow->setCursor(wcursor); } else { @@ -614,6 +621,27 @@ void QWindowsCursor::changeCursor(QCursor *cursorIn, QWindow *window) } } +void QWindowsCursor::setOverrideCursor(const QCursor &cursor) +{ + const CursorHandlePtr wcursor = cursorHandle(cursor); + if (wcursor->handle()) { + const HCURSOR previousCursor = SetCursor(wcursor->handle()); + if (m_overriddenCursor == nullptr) + m_overriddenCursor = previousCursor; + } else { + qWarning("%s: Unable to obtain system cursor for %d", + __FUNCTION__, cursor.shape()); + } +} + +void QWindowsCursor::clearOverrideCursor() +{ + if (m_overriddenCursor) { + SetCursor(m_overriddenCursor); + m_overriddenCursor = nullptr; + } +} + QPoint QWindowsCursor::mousePosition() { POINT p; diff --git a/src/plugins/platforms/windows/qwindowscursor.h b/src/plugins/platforms/windows/qwindowscursor.h index df2e22733b..28c7e88a6e 100644 --- a/src/plugins/platforms/windows/qwindowscursor.h +++ b/src/plugins/platforms/windows/qwindowscursor.h @@ -105,6 +105,9 @@ public: explicit QWindowsCursor(const QPlatformScreen *screen); void changeCursor(QCursor * widgetCursor, QWindow * widget) override; + void setOverrideCursor(const QCursor &cursor) override; + void clearOverrideCursor() override; + QPoint pos() const override; void setPos(const QPoint &pos) override; @@ -127,6 +130,8 @@ private: typedef QHash<Qt::CursorShape, CursorHandlePtr> StandardCursorCache; typedef QHash<QWindowsPixmapCursorCacheKey, CursorHandlePtr> PixmapCursorCache; + CursorHandlePtr cursorHandle(const QCursor &c); + const QPlatformScreen *const m_screen; StandardCursorCache m_standardCursorCache; PixmapCursorCache m_pixmapCursorCache; @@ -135,6 +140,8 @@ private: mutable QPixmap m_moveDragCursor; mutable QPixmap m_linkDragCursor; mutable QPixmap m_ignoreDragCursor; + + static HCURSOR m_overriddenCursor; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 4b08528d17..e713debf5b 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -39,8 +39,11 @@ #define QT_NO_URL_CAST_FROM_STRING 1 -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif +#include "qwindowscombase.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" @@ -52,7 +55,7 @@ #include <QtGui/QColor> #include <QtCore/QDebug> -#include <QtCore/QRegExp> +#include <QtCore/QRegularExpression> #include <QtCore/QTimer> #include <QtCore/QDir> #include <QtCore/QScopedArrayPointer> @@ -504,33 +507,11 @@ inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer<QFile class QWindowsNativeFileDialogBase; -class QWindowsNativeFileDialogEventHandler : public IFileDialogEvents +class QWindowsNativeFileDialogEventHandler : public QWindowsComBase<IFileDialogEvents> { public: static IFileDialogEvents *create(QWindowsNativeFileDialogBase *nativeFileDialog); - // IUnknown methods - IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) - { - if (riid != IID_IUnknown && riid != IID_IFileDialogEvents) { - *ppv = NULL; - return ResultFromScode(E_NOINTERFACE); - } - *ppv = this; - AddRef(); - return NOERROR; - } - - IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_ref); } - - IFACEMETHODIMP_(ULONG) Release() - { - const long ref = InterlockedDecrement(&m_ref); - if (!ref) - delete this; - return ref; - } - // IFileDialogEvents methods IFACEMETHODIMP OnFileOk(IFileDialog *); IFACEMETHODIMP OnFolderChange(IFileDialog *) { return S_OK; } @@ -546,7 +527,6 @@ public: virtual ~QWindowsNativeFileDialogEventHandler() {} private: - long m_ref = 1; QWindowsNativeFileDialogBase *m_nativeFileDialog; }; @@ -931,7 +911,7 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) // (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx) // specified as "clsid:<GUID>" (without '{', '}'). IShellItem *result = Q_NULLPTR; - const QUuid uuid(url.path()); + const auto uuid = QUuid::fromString(url.path()); if (uuid.isNull()) { qWarning() << __FUNCTION__ << ": Invalid CLSID: " << url.path(); return Q_NULLPTR; @@ -995,7 +975,9 @@ void QWindowsNativeFileDialogBase::setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options) { - DWORD flags = FOS_PATHMUSTEXIST | FOS_FORCESHOWHIDDEN; + DWORD flags = FOS_PATHMUSTEXIST; + if (QWindowsContext::readAdvancedExplorerSettings(L"Hidden", 1) == 1) // 1:show, 2:hidden + flags |= FOS_FORCESHOWHIDDEN; if (options & QFileDialogOptions::DontResolveSymlinks) flags |= FOS_NODEREFERENCELINKS; switch (mode) { @@ -1041,7 +1023,7 @@ static QList<FilterSpec> filterSpecs(const QStringList &filters, result.reserve(filters.size()); *totalStringLength = 0; - const QRegExp filterSeparatorRE(QStringLiteral("[;\\s]+")); + const QRegularExpression filterSeparatorRE(QStringLiteral("[;\\s]+")); const QString separator = QStringLiteral(";"); Q_ASSERT(filterSeparatorRE.isValid()); // Split filter specification as 'Texts (*.txt[;] *.doc)', '*.txt[;] *.doc' diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index 26403b68e5..aa6454ef63 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -44,7 +44,7 @@ # include "qwindowsclipboard.h" #endif #include "qwindowsintegration.h" -#include "qwindowsole.h" +#include "qwindowsdropdataobject.h" #include <QtCore/qt_windows.h> #include "qwindowswindow.h" #include "qwindowsmousehandler.h" @@ -215,7 +215,7 @@ static inline Qt::MouseButtons toQtMouseButtons(DWORD keyState) \ingroup qt-lighthouse-win */ -class QWindowsOleDropSource : public IDropSource +class QWindowsOleDropSource : public QWindowsComBase<IDropSource> { public: enum Mode { @@ -228,11 +228,6 @@ public: void createCursors(); - // IUnknown methods - STDMETHOD(QueryInterface)(REFIID riid, void ** ppvObj); - STDMETHOD_(ULONG,AddRef)(void); - STDMETHOD_(ULONG,Release)(void); - // IDropSource methods STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState); STDMETHOD(GiveFeedback)(DWORD dwEffect); @@ -257,7 +252,6 @@ private: ActionCursorMap m_cursors; QWindowsDragCursorWindow *m_touchDragWindow; - ULONG m_refs; #ifndef QT_NO_DEBUG_STREAM friend QDebug operator<<(QDebug, const QWindowsOleDropSource::CursorEntry &); #endif @@ -268,7 +262,6 @@ QWindowsOleDropSource::QWindowsOleDropSource(QWindowsDrag *drag) , m_drag(drag) , m_currentButtons(Qt::NoButton) , m_touchDragWindow(0) - , m_refs(1) { qCDebug(lcQpaMime) << __FUNCTION__ << m_mode; } @@ -373,38 +366,6 @@ void QWindowsOleDropSource::createCursors() #endif // !QT_NO_DEBUG_OUTPUT } -//--------------------------------------------------------------------- -// IUnknown Methods -//--------------------------------------------------------------------- - -STDMETHODIMP -QWindowsOleDropSource::QueryInterface(REFIID iid, void FAR* FAR* ppv) -{ - if (iid == IID_IUnknown || iid == IID_IDropSource) { - *ppv = this; - ++m_refs; - return NOERROR; - } - *ppv = NULL; - return ResultFromScode(E_NOINTERFACE); -} - -STDMETHODIMP_(ULONG) -QWindowsOleDropSource::AddRef(void) -{ - return ++m_refs; -} - -STDMETHODIMP_(ULONG) -QWindowsOleDropSource::Release(void) -{ - if (--m_refs == 0) { - delete this; - return 0; - } - return m_refs; -} - /*! \brief Check for cancel. */ @@ -509,34 +470,6 @@ QWindowsOleDropTarget::~QWindowsOleDropTarget() qCDebug(lcQpaMime) << __FUNCTION__ << this; } -STDMETHODIMP -QWindowsOleDropTarget::QueryInterface(REFIID iid, void FAR* FAR* ppv) -{ - if (iid == IID_IUnknown || iid == IID_IDropTarget) { - *ppv = this; - AddRef(); - return NOERROR; - } - *ppv = NULL; - return ResultFromScode(E_NOINTERFACE); -} - -STDMETHODIMP_(ULONG) -QWindowsOleDropTarget::AddRef(void) -{ - return ++m_refs; -} - -STDMETHODIMP_(ULONG) -QWindowsOleDropTarget::Release(void) -{ - if (--m_refs == 0) { - delete this; - return 0; - } - return m_refs; -} - void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState, const QPoint &point, LPDWORD pdwEffect) { @@ -740,7 +673,7 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag) QWindowsDrag::m_canceled = false; QWindowsOleDropSource *windowDropSource = new QWindowsOleDropSource(this); windowDropSource->createCursors(); - QWindowsOleDataObject *dropDataObject = new QWindowsOleDataObject(dropData); + QWindowsDropDataObject *dropDataObject = new QWindowsDropDataObject(dropData); const Qt::DropActions possibleActions = drag->supportedActions(); const DWORD allowedEffects = translateToWinDragEffects(possibleActions); qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x" diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h index 983f3a67b4..2b4ca2dce1 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.h +++ b/src/plugins/platforms/windows/qwindowsdrag.h @@ -40,6 +40,7 @@ #ifndef QWINDOWSDRAG_H #define QWINDOWSDRAG_H +#include "qwindowscombase.h" #include "qwindowsinternalmimedata.h" #include <qpa/qplatformdrag.h> @@ -57,17 +58,12 @@ public: IDataObject *retrieveDataObject() const override; }; -class QWindowsOleDropTarget : public IDropTarget +class QWindowsOleDropTarget : public QWindowsComBase<IDropTarget> { public: explicit QWindowsOleDropTarget(QWindow *w); virtual ~QWindowsOleDropTarget(); - // IUnknown methods - STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj); - STDMETHOD_(ULONG, AddRef)(void); - STDMETHOD_(ULONG, Release)(void); - // IDropTarget methods STDMETHOD(DragEnter)(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect); STDMETHOD(DragOver)(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect); @@ -77,7 +73,6 @@ public: private: void handleDrag(QWindow *window, DWORD grfKeyState, const QPoint &, LPDWORD pdwEffect); - ULONG m_refs = 1; QWindow *const m_window; QRect m_answerRect; QPoint m_lastPoint; @@ -91,8 +86,6 @@ public: QWindowsDrag(); virtual ~QWindowsDrag(); - QMimeData *platformDropData() override { return &m_dropData; } - Qt::DropAction drag(QDrag *drag) override; static QWindowsDrag *instance(); diff --git a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp new file mode 100644 index 0000000000..bd532ab70e --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#include "qwindowsdropdataobject.h" + +#include <QtCore/QUrl> +#include <QtCore/QMimeData> + +QT_BEGIN_NAMESPACE + +/*! + \class QWindowsDropDataObject + \brief QWindowsOleDataObject subclass specialized for handling Drag&Drop. + + Only allows "text/uri-list" data to be exported as CF_HDROP, to allow dropped + files to be attached to Office applications (instead of adding an URL link). + + \internal + \ingroup qt-lighthouse-win +*/ + +QWindowsDropDataObject::QWindowsDropDataObject(QMimeData *mimeData) : + QWindowsOleDataObject(mimeData) +{ +} + +QWindowsDropDataObject::~QWindowsDropDataObject() +{ +} + +STDMETHODIMP +QWindowsDropDataObject::GetData(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium) +{ + if (shouldIgnore(pformatetc)) + return ResultFromScode(DATA_E_FORMATETC); + + return QWindowsOleDataObject::GetData(pformatetc, pmedium); +} + +STDMETHODIMP +QWindowsDropDataObject::QueryGetData(LPFORMATETC pformatetc) +{ + if (shouldIgnore(pformatetc)) + return ResultFromScode(DATA_E_FORMATETC); + + return QWindowsOleDataObject::QueryGetData(pformatetc); +} + +// If the data is text/uri-list for local files, tell we can only export it as CF_HDROP. +bool QWindowsDropDataObject::shouldIgnore(LPFORMATETC pformatetc) const +{ + QMimeData *dropData = mimeData(); + + if (dropData && dropData->hasFormat(QStringLiteral("text/uri-list")) && (pformatetc->cfFormat != CF_HDROP)) { + QList<QUrl> urls = dropData->urls(); + return std::any_of(urls.cbegin(), urls.cend(), [] (const QUrl &u) { return u.isLocalFile(); }); + } + + return false; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsdropdataobject.h b/src/plugins/platforms/windows/qwindowsdropdataobject.h new file mode 100644 index 0000000000..5ef72c9336 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsdropdataobject.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSDROPDATAOBJECT_H +#define QWINDOWSDROPDATAOBJECT_H + +#include "qwindowsole.h" + +QT_BEGIN_NAMESPACE + +class QWindowsDropDataObject : public QWindowsOleDataObject +{ +public: + explicit QWindowsDropDataObject(QMimeData *mimeData); + virtual ~QWindowsDropDataObject(); + + // overridden IDataObject methods + STDMETHOD(GetData)(LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium); + STDMETHOD(QueryGetData)(LPFORMATETC pformatetc); + +private: + bool shouldIgnore(LPFORMATETC pformatetc) const; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSDROPDATAOBJECT_H diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index 751807e897..78368d87de 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp @@ -145,6 +145,10 @@ #define RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#ifndef WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20A9 +#endif + QT_BEGIN_NAMESPACE QWindowsOpengl32DLL QOpenGLStaticContext::opengl32; @@ -489,7 +493,7 @@ static int choosePixelFormat(HDC hdc, const QWindowsOpenGLAdditionalFormat &additional, PIXELFORMATDESCRIPTOR *obtainedPfd) { - enum { attribSize =40 }; + enum { attribSize = 42 }; if ((additional.formatFlags & QWindowsGLRenderToPixmap) || !staticContext.hasExtensions()) return 0; @@ -570,7 +574,15 @@ static int choosePixelFormat(HDC hdc, iAttributes[i++] = WGL_SAMPLE_BUFFERS_ARB; iAttributes[i++] = FALSE; } - // If sample buffer request cannot be satisfied, reduce request. + // must be the last + bool srgbRequested = format.colorSpace() == QSurfaceFormat::sRGBColorSpace; + int srgbValuePosition = 0; + if (srgbRequested) { + srgbValuePosition = i; + iAttributes[i++] = WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT; + iAttributes[i++] = TRUE; + } + // If sample or sRGB request cannot be satisfied, reduce request. int pixelFormat = 0; uint numFormats = 0; while (true) { @@ -578,20 +590,25 @@ static int choosePixelFormat(HDC hdc, staticContext.wglChoosePixelFormatARB(hdc, iAttributes, 0, 1, &pixelFormat, &numFormats) && numFormats >= 1; - if (valid || !sampleBuffersRequested) - break; - if (iAttributes[samplesValuePosition] > 1) { - iAttributes[samplesValuePosition] /= 2; - } else if (iAttributes[samplesValuePosition] == 1) { - // Fallback in case it is unable to initialize with any - // samples to avoid falling back to the GDI path - // NB: The sample attributes needs to be at the end for this - // to work correctly - iAttributes[samplesValuePosition - 1] = FALSE; - iAttributes[samplesValuePosition] = 0; - iAttributes[samplesValuePosition + 1] = 0; - } else { + if (valid || (!sampleBuffersRequested && !srgbRequested)) break; + if (srgbRequested) { + iAttributes[srgbValuePosition] = 0; + srgbRequested = false; + } else if (sampleBuffersRequested) { + if (iAttributes[samplesValuePosition] > 1) { + iAttributes[samplesValuePosition] /= 2; + } else if (iAttributes[samplesValuePosition] == 1) { + // Fallback in case it is unable to initialize with any + // samples to avoid falling back to the GDI path + // NB: The sample attributes needs to be at the end for this + // to work correctly + iAttributes[samplesValuePosition - 1] = FALSE; + iAttributes[samplesValuePosition] = 0; + iAttributes[samplesValuePosition + 1] = 0; + } else { + break; + } } } // Verify if format is acceptable. Note that the returned @@ -628,7 +645,7 @@ static QSurfaceFormat HDC hdc, int pixelFormat, QWindowsOpenGLAdditionalFormat *additionalIn = 0) { - enum { attribSize =40 }; + enum { attribSize = 42 }; QSurfaceFormat result; result.setRenderableType(QSurfaceFormat::OpenGL); @@ -641,6 +658,7 @@ static QSurfaceFormat int i = 0; const bool hasSampleBuffers = testFlag(staticContext.extensions, QOpenGLStaticContext::SampleBuffers); + const bool hasSrgbSupport = testFlag(staticContext.extensions, QOpenGLStaticContext::sRGBCapableFramebuffer); iAttributes[i++] = WGL_DOUBLE_BUFFER_ARB; // 0 iAttributes[i++] = WGL_DEPTH_BITS_ARB; // 1 @@ -658,6 +676,9 @@ static QSurfaceFormat iAttributes[i++] = WGL_SAMPLE_BUFFERS_ARB; // 12 iAttributes[i++] = WGL_SAMPLES_ARB; // 13 } + if (hasSrgbSupport) + iAttributes[i++] = WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT; // 12 or 14 + if (!staticContext.wglGetPixelFormatAttribIVARB(hdc, pixelFormat, 0, i, iAttributes, iValues)) { qErrnoWarning("%s: wglGetPixelFormatAttribIVARB() failed for basic parameters.", __FUNCTION__); @@ -673,8 +694,14 @@ static QSurfaceFormat if (iValues[9]) result.setOption(QSurfaceFormat::StereoBuffers); - if (hasSampleBuffers) + if (hasSampleBuffers) { result.setSamples(iValues[13]); + if (hasSrgbSupport && iValues[14]) + result.setColorSpace(QSurfaceFormat::sRGBColorSpace); + } else { + if (hasSrgbSupport && iValues[12]) + result.setColorSpace(QSurfaceFormat::sRGBColorSpace); + } if (additionalIn) { if (iValues[7]) additionalIn->formatFlags |= QWindowsGLAccumBuffer; @@ -947,7 +974,8 @@ QOpenGLStaticContext::QOpenGLStaticContext() : wglChoosePixelFormatARB((WglChoosePixelFormatARB)QOpenGLStaticContext::opengl32.wglGetProcAddress("wglChoosePixelFormatARB")), wglCreateContextAttribsARB((WglCreateContextAttribsARB)QOpenGLStaticContext::opengl32.wglGetProcAddress("wglCreateContextAttribsARB")), wglSwapInternalExt((WglSwapInternalExt)QOpenGLStaticContext::opengl32.wglGetProcAddress("wglSwapIntervalEXT")), - wglGetSwapInternalExt((WglGetSwapInternalExt)QOpenGLStaticContext::opengl32.wglGetProcAddress("wglGetSwapIntervalEXT")) + wglGetSwapInternalExt((WglGetSwapInternalExt)QOpenGLStaticContext::opengl32.wglGetProcAddress("wglGetSwapIntervalEXT")), + wglGetExtensionsStringARB((WglGetExtensionsStringARB)QOpenGLStaticContext::opengl32.wglGetProcAddress("wglGetExtensionsStringARB")) { if (extensionNames.startsWith(SAMPLE_BUFFER_EXTENSION " ") || extensionNames.indexOf(" " SAMPLE_BUFFER_EXTENSION " ") != -1) @@ -1091,6 +1119,14 @@ QWindowsGLContext::QWindowsGLContext(QOpenGLStaticContext *staticContext, && !(QWindowsIntegration::instance()->options() & QWindowsIntegration::DisableArb); QWindowsOpenGLAdditionalFormat obtainedAdditional; if (tryExtensions) { + if (m_staticContext->wglGetExtensionsStringARB) { + const char *exts = m_staticContext->wglGetExtensionsStringARB(hdc); + if (exts) { + qCDebug(lcQpaGl) << __FUNCTION__ << "WGL extensions:" << exts; + if (strstr(exts, "WGL_EXT_framebuffer_sRGB")) + m_staticContext->extensions |= QOpenGLStaticContext::sRGBCapableFramebuffer; + } + } m_pixelFormat = ARB::choosePixelFormat(hdc, *m_staticContext, format, requestedAdditional, &m_obtainedPixelFormatDescriptor); diff --git a/src/plugins/platforms/windows/qwindowsglcontext.h b/src/plugins/platforms/windows/qwindowsglcontext.h index dfaa428520..2d5b94af0e 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.h +++ b/src/plugins/platforms/windows/qwindowsglcontext.h @@ -139,7 +139,8 @@ class QOpenGLStaticContext : public QWindowsStaticOpenGLContext public: enum Extensions { - SampleBuffers = 0x1 + SampleBuffers = 0x1, + sRGBCapableFramebuffer = 0x2 }; typedef bool @@ -160,6 +161,9 @@ public: typedef int (APIENTRY *WglGetSwapInternalExt)(void); + typedef const char * + (APIENTRY *WglGetExtensionsStringARB)(HDC); + bool hasExtensions() const { return wglGetPixelFormatAttribIVARB && wglChoosePixelFormatARB && wglCreateContextAttribsARB; } @@ -185,6 +189,7 @@ public: WglCreateContextAttribsARB wglCreateContextAttribsARB; WglSwapInternalExt wglSwapInternalExt; WglGetSwapInternalExt wglGetSwapInternalExt; + WglGetExtensionsStringARB wglGetExtensionsStringARB; static QWindowsOpengl32DLL opengl32; }; diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 17cab69891..f9bac3920b 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -42,6 +42,7 @@ #include "qwindowswindow.h" #include "qwindowscontext.h" #include "qwin10helpers.h" +#include "qwindowsmenu.h" #include "qwindowsopenglcontext.h" #include "qwindowsscreen.h" @@ -71,6 +72,7 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qhighdpiscaling_p.h> #include <QtGui/qpa/qplatforminputcontextfactory_p.h> +#include <QtGui/qpa/qplatformcursor.h> #include <QtEventDispatcherSupport/private/qwindowsguieventdispatcher_p.h> @@ -206,6 +208,10 @@ static inline unsigned parseOptions(const QStringList ¶mList, } else if (parseIntOption(param, QLatin1String("verbose"), 0, INT_MAX, &QWindowsContext::verbose) || parseIntOption(param, QLatin1String("tabletabsoluterange"), 0, INT_MAX, tabletAbsoluteRange) || parseIntOption(param, QLatin1String("dpiawareness"), QtWindows::ProcessDpiUnaware, QtWindows::ProcessPerMonitorDpiAware, dpiAwareness)) { + } else if (param == QLatin1String("menus=native")) { + options |= QWindowsIntegration::AlwaysUseNativeMenus; + } else if (param == QLatin1String("menus=none")) { + options |= QWindowsIntegration::NoNativeMenus; } else { qWarning() << "Unknown option" << param; } @@ -237,6 +243,7 @@ QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(const QStringList ¶mL } m_context.initTouch(m_options); + QPlatformCursor::setCapability(QPlatformCursor::OverrideCursor); } QWindowsIntegrationPrivate::~QWindowsIntegrationPrivate() @@ -334,20 +341,8 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons QWindowsWindow *result = createPlatformWindowHelper(window, obtained); Q_ASSERT(result); - if (requested.flags != obtained.flags) - window->setFlags(obtained.flags); - // Trigger geometry change (unless it has a special state in which case setWindowState() - // will send the message) and screen change signals of QWindow. - if ((obtained.flags & Qt::Desktop) != Qt::Desktop) { - const Qt::WindowState state = window->windowState(); - if (state != Qt::WindowMaximized && state != Qt::WindowFullScreen - && requested.geometry != obtained.geometry) { - QWindowSystemInterface::handleGeometryChange(window, obtained.geometry); - } - QPlatformScreen *screen = result->screenForGeometry(obtained.geometry); - if (screen && result->screen() != screen) - QWindowSystemInterface::handleWindowScreenChanged(window, screen->screen()); - } + if (QWindowsMenuBar *menuBarToBeInstalled = QWindowsMenuBar::menuBarOf(window)) + menuBarToBeInstalled->install(result); return result; } @@ -611,4 +606,11 @@ void QWindowsIntegration::beep() const MessageBeep(MB_OK); // For QApplication } +#if QT_CONFIG(vulkan) +QPlatformVulkanInstance *QWindowsIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const +{ + return new QWindowsVulkanInstance(instance); +} +#endif + QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index 28d4fd3026..23f3d9ef4e 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -64,7 +64,9 @@ public: DontPassOsMouseEventsSynthesizedFromTouch = 0x20, // Do not pass OS-generated mouse events from touch. // Keep in sync with QWindowsFontDatabase::FontOptions DontUseDirectWriteFonts = QWindowsFontDatabase::DontUseDirectWriteFonts, - DontUseColorFonts = QWindowsFontDatabase::DontUseColorFonts + DontUseColorFonts = QWindowsFontDatabase::DontUseColorFonts, + AlwaysUseNativeMenus = 0x100, + NoNativeMenus = 0x200 }; explicit QWindowsIntegration(const QStringList ¶mList); @@ -113,6 +115,10 @@ public: QPlatformSessionManager *createPlatformSessionManager(const QString &id, const QString &key) const override; #endif +#if QT_CONFIG(vulkan) + QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; +#endif + protected: virtual QWindowsWindow *createPlatformWindowHelper(QWindow *window, const QWindowsWindowData &) const; diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp index 3987d8ca29..af62936a18 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -903,6 +903,12 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &ms return true; } + // Enable Alt accelerators ("&File") on menus + if (msgType == WM_SYSKEYDOWN && (nModifiers & AltAny) != 0 && GetMenu(msg.hwnd) != nullptr) + return false; + if (msgType == WM_SYSKEYUP && nModifiers == 0 && GetMenu(msg.hwnd) != nullptr) + return false; + bool result = false; // handle Directionality changes (BiDi) with RTL extensions if (m_useRTLExtensions) { diff --git a/src/plugins/platforms/windows/qwindowsmenu.cpp b/src/plugins/platforms/windows/qwindowsmenu.cpp new file mode 100644 index 0000000000..72f11d54b4 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsmenu.cpp @@ -0,0 +1,969 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#include "qwindowsmenu.h" +#include "qwindowscontext.h" +#include "qwindowswindow.h" + +#include <QtGui/qwindow.h> +#include <QtCore/qdebug.h> +#include <QtCore/qvariant.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qpointer.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/*! + \class QWindowsMenuBar + \brief Windows native menu bar + + \list + \li \l{https://msdn.microsoft.com/de-de/library/windows/desktop/ms647553(v=vs.85).aspx#_win32_Menu_Creation_Functions}, + \e{About Menus} + \endlist + + \note The destruction order of the QWindowsMenu/Item/Bar instances is + arbitrary depending on whether the application is Qt Quick or + Qt Widgets, either the containers or the items might be deleted first. + + \internal + \ingroup qt-lighthouse-win +*/ + +static uint nextId = 1; + +// Find a QPlatformMenu[Item]* in a vector of QWindowsMenu[Item], where +// QVector::indexOf() cannot be used since it wants a QWindowsMenu[Item]* +template <class Derived, class Needle> +static int indexOf(const QVector<Derived *> &v, const Needle *needle) +{ + for (int i = 0, size = v.size(); i < size; ++i) { + if (v.at(i) == needle) + return i; + } + return -1; +} + +// Helper for inserting a QPlatformMenu[Item]* into a vector of QWindowsMenu[Item]. +template <class Derived, class Base> +static int insertBefore(QVector<Derived *> *v, Base *newItemIn, const Base *before = nullptr) +{ + int index = before ? indexOf(*v, before) : -1; + if (index != -1) { + v->insert(index, static_cast<Derived *>(newItemIn)); + } else { + index = v->size(); + v->append(static_cast<Derived *>(newItemIn)); + } + return index; +} + +static inline const wchar_t *qStringToWChar(const QString &s) +{ + return reinterpret_cast<const wchar_t *>(s.utf16()); +} + +// Traverse menu and return the item for which predicate +// "bool Function(QWindowsMenuItem *)" returns true +template <class Predicate> +static QWindowsMenuItem *traverseMenuItems(const QWindowsMenu *menu, Predicate p) +{ + const QWindowsMenu::MenuItems &items = menu->menuItems(); + for (QWindowsMenuItem *item : items) { + if (p(item)) + return item; + if (item->subMenu()) { + if (QWindowsMenuItem *subMenuItem = traverseMenuItems(item->subMenu(), p)) + return subMenuItem; + } + } + return nullptr; +} + +// Traverse menu bar return the item for which predicate +// "bool Function(QWindowsMenuItem *)" returns true +template <class Predicate> +static QWindowsMenuItem *traverseMenuItems(const QWindowsMenuBar *menuBar, Predicate p) +{ + const QWindowsMenuBar::Menus &menus = menuBar->menus(); + for (QWindowsMenu *menu : menus) { + if (QWindowsMenuItem *item = traverseMenuItems(menu, p)) + return item; + } + return nullptr; +} + +template <class Menu /* Menu[Bar] */> +static QWindowsMenuItem *findMenuItemById(const Menu *menu, uint id) +{ + return traverseMenuItems(menu, [id] (const QWindowsMenuItem *i) { return i->id() == id; }); +} + +// Traverse menu and return the menu for which predicate +// "bool Function(QWindowsMenu *)" returns true +template <class Predicate> +static QWindowsMenu *traverseMenus(const QWindowsMenu *menu, Predicate p) +{ + const QWindowsMenu::MenuItems &items = menu->menuItems(); + for (QWindowsMenuItem *item : items) { + if (QWindowsMenu *subMenu = item->subMenu()) { + if (p(subMenu)) + return subMenu; + if (QWindowsMenu *menu = traverseMenus(subMenu, p)) + return menu; + } + } + return nullptr; +} + +// Traverse menu bar return the item for which +// function "bool Function(QWindowsMenu *)" returns true +template <class Predicate> +static QWindowsMenu *traverseMenus(const QWindowsMenuBar *menuBar, Predicate p) +{ + const QWindowsMenuBar::Menus &menus = menuBar->menus(); + for (QWindowsMenu *menu : menus) { + if (p(menu)) + return menu; + if (QWindowsMenu *subMenu = traverseMenus(menu, p)) + return subMenu; + } + return nullptr; +} + +template <class Menu /* Menu[Bar] */> +static QWindowsMenu *findMenuByHandle(const Menu *menu, HMENU hMenu) +{ + return traverseMenus(menu, [hMenu] (const QWindowsMenu *i) { return i->menuHandle() == hMenu; }); +} + +template <class MenuType> +static int findNextVisibleEntry(const QVector<MenuType *> &entries, int pos) +{ + for (int i = pos, size = entries.size(); i < size; ++i) { + if (entries.at(i)->isVisible()) + return i; + } + return -1; +} + +static inline void menuItemInfoInit(MENUITEMINFO &menuItemInfo) +{ + memset(&menuItemInfo, 0, sizeof(MENUITEMINFO)); + menuItemInfo.cbSize = sizeof(MENUITEMINFO); +} + +static inline void menuItemInfoSetText(MENUITEMINFO &menuItemInfo, const QString &text) +{ + menuItemInfoInit(menuItemInfo); + menuItemInfo.fMask = MIIM_STRING; + menuItemInfo.dwTypeData = const_cast<wchar_t *>(qStringToWChar(text)); + menuItemInfo.cch = UINT(text.size()); +} + +static UINT menuItemState(HMENU hMenu, UINT uItem, BOOL fByPosition) +{ + MENUITEMINFO menuItemInfo; + menuItemInfoInit(menuItemInfo); + menuItemInfo.fMask = MIIM_STATE; + return GetMenuItemInfo(hMenu, uItem, fByPosition, &menuItemInfo) == TRUE ? menuItemInfo.fState : 0; +} + +static void menuItemSetState(HMENU hMenu, UINT uItem, BOOL fByPosition, UINT flags) +{ + MENUITEMINFO menuItemInfo; + menuItemInfoInit(menuItemInfo); + menuItemInfo.fMask = MIIM_STATE; + menuItemInfo.fState = flags; + SetMenuItemInfo(hMenu, uItem, fByPosition, &menuItemInfo); +} + +static void menuItemSetChangeState(HMENU hMenu, UINT uItem, BOOL fByPosition, + bool value, UINT trueState, UINT falseState) +{ + const UINT oldState = menuItemState(hMenu, uItem, fByPosition); + UINT newState = oldState; + if (value) { + newState |= trueState; + newState &= ~falseState; + } else { + newState &= ~trueState; + newState |= falseState; + } + if (oldState != newState) + menuItemSetState(hMenu, uItem, fByPosition, newState); +} + +// ------------ QWindowsMenuItem +QWindowsMenuItem::QWindowsMenuItem(QWindowsMenu *parentMenu) + : m_parentMenu(parentMenu) + , m_id(0) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this) + << "parentMenu=" << parentMenu; +} + +QWindowsMenuItem::~QWindowsMenuItem() +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this); + removeFromMenu(); + freeBitmap(); +} + +void QWindowsMenuItem::freeBitmap() +{ + if (m_hbitmap) { + DeleteObject(m_hbitmap); + m_hbitmap = nullptr; + } +} + +void QWindowsMenuItem::setIcon(const QIcon &icon) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << icon << ')' << this; + if (m_icon.cacheKey() == icon.cacheKey()) + return; + m_icon = icon; + if (m_parentMenu != nullptr) + updateBitmap(); +} + +Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0); + +void QWindowsMenuItem::updateBitmap() +{ + freeBitmap(); + if (!m_icon.isNull()) { + const int size = m_iconSize ? m_iconSize : GetSystemMetrics(SM_CYMENUCHECK); + m_hbitmap = qt_pixmapToWinHBITMAP(m_icon.pixmap(QSize(size, size)), 1); + } + MENUITEMINFO itemInfo; + menuItemInfoInit(itemInfo); + itemInfo.fMask = MIIM_BITMAP; + itemInfo.hbmpItem = m_hbitmap; + SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &itemInfo); +} + +void QWindowsMenuItem::setText(const QString &text) +{ + qCDebug(lcQpaMenus).nospace().noquote() + << __FUNCTION__ << "(\"" << text << "\") " << this; + if (m_text == text) + return; + m_text = text; + if (m_parentMenu != nullptr) + updateText(); +} + +void QWindowsMenuItem::updateText() +{ + MENUITEMINFO menuItemInfo; + const QString &text = nativeText(); + menuItemInfoSetText(menuItemInfo, text); + SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &menuItemInfo); +} + +void QWindowsMenuItem::setMenu(QPlatformMenu *menuIn) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuIn << ')' << this; + if (menuIn == m_subMenu) + return; + const uint oldId = m_id; + if (menuIn != nullptr) { // Set submenu + m_subMenu = static_cast<QWindowsMenu *>(menuIn); + m_subMenu->setAsItemSubMenu(this); + m_id = m_subMenu->id(); + if (m_parentMenu != nullptr) { + ModifyMenu(m_parentMenu->menuHandle(), oldId, MF_BYCOMMAND | MF_POPUP, + m_id, qStringToWChar(m_text)); + } + return; + } + // Clear submenu + m_subMenu = nullptr; + if (m_parentMenu != nullptr) { + m_id = nextId++; + ModifyMenu(m_parentMenu->menuHandle(), oldId, MF_BYCOMMAND, + m_id, qStringToWChar(m_text)); + } else { + m_id = 0; + } +} + +void QWindowsMenuItem::setVisible(bool isVisible) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isVisible << ')' << this; + if (m_visible == isVisible) + return; + m_visible = isVisible; + if (m_parentMenu == nullptr) + return; + // Windows menu items do not implement settable visibility, we need to work + // around by removing the item from the menu. It will be kept in the list. + if (isVisible) + insertIntoMenuHelper(m_parentMenu, false, m_parentMenu->menuItems().indexOf(this)); + else + RemoveMenu(parentMenuHandle(), m_id, MF_BYCOMMAND); +} + +void QWindowsMenuItem::setIsSeparator(bool isSeparator) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isSeparator << ')' << this; + if (m_separator == isSeparator) + return; + m_separator = isSeparator; +} + +void QWindowsMenuItem::setCheckable(bool checkable) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << checkable << ')' << this; + if (m_checkable == checkable) + return; + m_checkable = checkable; + if (m_parentMenu == nullptr) + return; + UINT state = menuItemState(parentMenuHandle(), m_id, FALSE); + if (m_checkable) + state |= m_checked ? MF_CHECKED : MF_UNCHECKED; + else + state &= ~(MF_CHECKED | MF_UNCHECKED); + menuItemSetState(parentMenuHandle(), m_id, FALSE, state); +} + +void QWindowsMenuItem::setChecked(bool isChecked) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isChecked << ')' << this; + if (m_checked == isChecked) + return; + m_checked = isChecked; + // Convenience: Allow to set checkable by calling setChecked(true) for + // Quick Controls 1 + if (isChecked) + m_checkable = true; + if (m_parentMenu == nullptr || !m_checkable) + return; + menuItemSetChangeState(parentMenuHandle(), m_id, FALSE, m_checked, MF_CHECKED, MF_UNCHECKED); +} + +void QWindowsMenuItem::setShortcut(const QKeySequence &shortcut) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << shortcut << ')' << this; + if (m_shortcut == shortcut) + return; + m_shortcut = shortcut; + if (m_parentMenu != nullptr) + updateText(); +} + +void QWindowsMenuItem::setEnabled(bool enabled) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << enabled << ')' << this; + if (m_enabled == enabled) + return; + m_enabled = enabled; + if (m_parentMenu != nullptr) + menuItemSetChangeState(parentMenuHandle(), m_id, FALSE, m_enabled, MF_ENABLED, MF_GRAYED); +} + +void QWindowsMenuItem::setIconSize(int size) +{ + if (m_iconSize == size) + return; + m_iconSize = size; + if (m_parentMenu != nullptr) + updateBitmap(); +} + +HMENU QWindowsMenuItem::parentMenuHandle() const +{ + return m_parentMenu ? m_parentMenu->menuHandle() : nullptr; +} + +UINT QWindowsMenuItem::state() const +{ + if (m_separator) + return MF_SEPARATOR; + UINT result = MF_STRING | (m_enabled ? MF_ENABLED : MF_GRAYED); + if (m_subMenu != nullptr) + result |= MF_POPUP; + if (m_checkable) + result |= m_checked ? MF_CHECKED : MF_UNCHECKED; + if (QGuiApplication::layoutDirection() == Qt::RightToLeft) + result |= MFT_RIGHTORDER; + return result; +} + +QString QWindowsMenuItem::nativeText() const +{ + QString result = m_text; + if (!m_shortcut.isEmpty()) { + result += QLatin1Char('\t'); + result += m_shortcut.toString(QKeySequence::NativeText); + } + return result; +} + +void QWindowsMenuItem::insertIntoMenu(QWindowsMenu *menu, bool append, int index) +{ + if (m_id == 0 && m_subMenu == nullptr) + m_id = nextId++; + insertIntoMenuHelper(menu, append, index); + m_parentMenu = menu; +} + +void QWindowsMenuItem::insertIntoMenuHelper(QWindowsMenu *menu, bool append, int index) +{ + const QString &text = nativeText(); + + UINT_PTR idBefore = 0; + if (!append) { + // Skip over self (either newly inserted or when called from setVisible() + const int nextIndex = findNextVisibleEntry(menu->menuItems(), index + 1); + if (nextIndex != -1) + idBefore = menu->menuItems().at(nextIndex)->id(); + } + + if (idBefore) + InsertMenu(menu->menuHandle(), idBefore, state(), m_id, qStringToWChar(text)); + else + AppendMenu(menu->menuHandle(), state(), m_id, qStringToWChar(text)); + + updateBitmap(); +} + +bool QWindowsMenuItem::removeFromMenu() +{ + if (QWindowsMenu *parentMenu = m_parentMenu) { + m_parentMenu = nullptr; + RemoveMenu(parentMenu->menuHandle(), m_id, MF_BYCOMMAND); + parentMenu->notifyRemoved(this); + return true; + } + return false; +} + +// ------------ QWindowsMenu + +QWindowsMenu::QWindowsMenu() : QWindowsMenu(nullptr, CreateMenu()) +{ +} + +QWindowsMenu::QWindowsMenu(QWindowsMenu *parentMenu, HMENU menu) + : m_parentMenu(parentMenu) + , m_hMenu(menu) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this) + << "parentMenu=" << parentMenu << "HMENU=" << m_hMenu; +} + +QWindowsMenu::~QWindowsMenu() +{ + qCDebug(lcQpaMenus).noquote().nospace() << __FUNCTION__ + << " \"" <<m_text << "\", " << static_cast<const void *>(this); + for (int i = m_menuItems.size() - 1; i>= 0; --i) + m_menuItems.at(i)->removeFromMenu(); + removeFromParent(); + DestroyMenu(m_hMenu); +} + +void QWindowsMenu::insertMenuItem(QPlatformMenuItem *menuItemIn, QPlatformMenuItem *before) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuItemIn << ", before=" << before << ')' << this; + QWindowsMenuItem *menuItem = static_cast<QWindowsMenuItem *>(menuItemIn); + const int index = insertBefore(&m_menuItems, menuItemIn, before); + const bool append = index == m_menuItems.size() - 1; + menuItem->insertIntoMenu(this, append, index); +} + +void QWindowsMenu::removeMenuItem(QPlatformMenuItem *menuItemIn) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuItemIn << ')' << this; + static_cast<QWindowsMenuItem *>(menuItemIn)->removeFromMenu(); +} + +void QWindowsMenu::setText(const QString &text) +{ + qCDebug(lcQpaMenus).nospace().noquote() + << __FUNCTION__ << "(\"" << text << "\") " << this; + if (m_text == text) + return; + m_text = text; + if (!m_visible) + return; + const HMENU ph = parentHandle(); + if (ph == nullptr) + return; + MENUITEMINFO menuItemInfo; + menuItemInfoSetText(menuItemInfo, m_text); + SetMenuItemInfo(ph, id(), FALSE, &menuItemInfo); +} + +void QWindowsMenu::setIcon(const QIcon &icon) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << icon << ')' << this; + m_icon = icon; +} + +void QWindowsMenu::setEnabled(bool enabled) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << enabled << ')' << this; + if (m_enabled == enabled) + return; + m_enabled = enabled; + if (!m_visible) + return; + if (const HMENU ph = parentHandle()) + menuItemSetChangeState(ph, id(), FALSE, m_enabled, MF_ENABLED, MF_GRAYED); +} + +QWindowsMenuItem *QWindowsMenu::itemForSubMenu(const QWindowsMenu *subMenu) const +{ + const auto it = std::find_if(m_menuItems.cbegin(), m_menuItems.cend(), + [subMenu] (const QWindowsMenuItem *i) { return i->subMenu() == subMenu; }); + return it != m_menuItems.cend() ? *it : nullptr; +} + +void QWindowsMenu::insertIntoMenuBar(QWindowsMenuBar *bar, bool append, int index) +{ + UINT_PTR idBefore = 0; + if (!append) { + // Skip over self (either newly inserted or when called from setVisible() + const int nextIndex = findNextVisibleEntry(bar->menus(), index + 1); + if (nextIndex != -1) + idBefore = bar->menus().at(nextIndex)->id(); + } + m_parentMenuBar = bar; + m_parentMenu = nullptr; + if (idBefore) + InsertMenu(bar->menuBarHandle(), idBefore, MF_POPUP | MF_BYCOMMAND, id(), qStringToWChar(m_text)); + else + AppendMenu(bar->menuBarHandle(), MF_POPUP, id(), qStringToWChar(m_text)); +} + +bool QWindowsMenu::removeFromParent() +{ + if (QWindowsMenuBar *bar = m_parentMenuBar) { + m_parentMenuBar = nullptr; + bar->notifyRemoved(this); + return RemoveMenu(bar->menuBarHandle(), id(), MF_BYCOMMAND) == TRUE; + } + if (QWindowsMenu *menu = m_parentMenu) { + m_parentMenu = nullptr; + QWindowsMenuItem *item = menu->itemForSubMenu(this); + if (item) + item->setMenu(nullptr); + return item != nullptr; + } + return false; +} + +void QWindowsMenu::setVisible(bool visible) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << visible << ')' << this; + if (m_visible == visible) + return; + m_visible = visible; + const HMENU ph = parentHandle(); + if (ph == nullptr) + return; + // Windows menus do not implement settable visibility, we need to work + // around by removing the menu from the parent. It will be kept in the list. + if (visible) { + if (m_parentMenuBar) + insertIntoMenuBar(m_parentMenuBar, false, m_parentMenuBar->menus().indexOf(this)); + } else { + RemoveMenu(ph, id(), MF_BYCOMMAND); + } + if (m_parentMenuBar) + m_parentMenuBar->redraw(); +} + +QPlatformMenuItem *QWindowsMenu::menuItemAt(int position) const +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << position; + return position >= 0 && position < m_menuItems.size() + ? m_menuItems.at(position) : nullptr; +} + +QPlatformMenuItem *QWindowsMenu::menuItemForTag(quintptr tag) const +{ + return traverseMenuItems(this, [tag] (const QPlatformMenuItem *i) { return i->tag() == tag; }); +} + +QPlatformMenuItem *QWindowsMenu::createMenuItem() const +{ + QPlatformMenuItem *result = new QWindowsMenuItem; + qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result; + return result; +} + +QPlatformMenu *QWindowsMenu::createSubMenu() const +{ + QPlatformMenu *result = new QWindowsMenu; + qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result; + return result; +} + +void QWindowsMenu::setAsItemSubMenu(QWindowsMenuItem *item) +{ + m_parentMenu = item->parentMenu(); +} + +HMENU QWindowsMenu::parentMenuHandle() const +{ + return m_parentMenu ? m_parentMenu->menuHandle() : nullptr; +} + +HMENU QWindowsMenu::parentMenuBarHandle() const +{ + return m_parentMenuBar ? m_parentMenuBar->menuBarHandle() : nullptr; +} + +HMENU QWindowsMenu::parentHandle() const +{ + if (m_parentMenuBar) + return m_parentMenuBar->menuBarHandle(); + if (m_parentMenu) + return m_parentMenu->menuHandle(); + return nullptr; +} + +// --------------- QWindowsPopupMenu + +static QPointer<QWindowsPopupMenu> lastShownPopupMenu; + +QWindowsPopupMenu::QWindowsPopupMenu() : QWindowsMenu(nullptr, CreatePopupMenu()) +{ +} + +void QWindowsPopupMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, + const QPlatformMenuItem *item) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '>' << this << parentWindow << targetRect << item; + const QWindowsBaseWindow *window = static_cast<const QWindowsBaseWindow *>(parentWindow->handle()); + const QPoint globalPos = window->mapToGlobal(targetRect.topLeft()); + trackPopupMenu(window->handle(), globalPos.x(), globalPos.y()); +} + +bool QWindowsPopupMenu::trackPopupMenu(HWND windowHandle, int x, int y) +{ + lastShownPopupMenu = this; + // Emulate Show()/Hide() signals. Could be implemented by catching the + // WM_EXITMENULOOP, WM_ENTERMENULOOP messages; but they do not carry + // information telling which menu was opened. + emit aboutToShow(); + const bool result = + TrackPopupMenu(menuHandle(), + QGuiApplication::layoutDirection() == Qt::RightToLeft ? UINT(TPM_RIGHTALIGN) : UINT(0), + x, y, 0, windowHandle, nullptr) == TRUE; + emit aboutToHide(); + return result; +} + +bool QWindowsPopupMenu::notifyTriggered(uint id) +{ + QPlatformMenuItem *result = lastShownPopupMenu.isNull() + ? nullptr + : findMenuItemById(lastShownPopupMenu.data(), id); + if (result != nullptr) { + qCDebug(lcQpaMenus) << __FUNCTION__ << "id=" << id; + emit result->activated(); + } + lastShownPopupMenu = nullptr; + return result != nullptr; +} + +bool QWindowsPopupMenu::notifyAboutToShow(HMENU hmenu) +{ + if (lastShownPopupMenu.isNull()) + return false; + if (lastShownPopupMenu->menuHandle() == hmenu) { + emit lastShownPopupMenu->aboutToShow(); + return true; + } + if (QWindowsMenu *menu = findMenuByHandle(lastShownPopupMenu.data(), hmenu)) { + emit menu->aboutToShow(); + return true; + } + return false; +} + +// --------------- QWindowsMenuBar + +QWindowsMenuBar::QWindowsMenuBar() : m_hMenuBar(CreateMenu()) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this); +} + +QWindowsMenuBar::~QWindowsMenuBar() +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this); + for (int m = m_menus.size() - 1; m >= 0; --m) + m_menus.at(m)->removeFromParent(); + removeFromWindow(); + DestroyMenu(m_hMenuBar); +} + +void QWindowsMenuBar::insertMenu(QPlatformMenu *menuIn, QPlatformMenu *before) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << menuIn << "before=" << before; + QWindowsMenu *menu = static_cast<QWindowsMenu *>(menuIn); + const int index = insertBefore(&m_menus, menuIn, before); + menu->insertIntoMenuBar(this, index == m_menus.size() - 1, index); +} + +void QWindowsMenuBar::removeMenu(QPlatformMenu *menu) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menu << ')' << this; + const int index = indexOf(m_menus, menu); + if (index != -1) + m_menus[index]->removeFromParent(); +} + +// When calling handleReparent() for a QWindow instances that does not have +// a platform window yet, set the menubar as dynamic property to be installed +// on platform window creation. +static const char menuBarPropertyName[] = "_q_windowsNativeMenuBar"; + +void QWindowsMenuBar::handleReparent(QWindow *newParentWindow) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << newParentWindow << ')' << this; + if (newParentWindow == nullptr) { + removeFromWindow(); + return; // Happens during Quick Controls 1 property setup + } + if (QPlatformWindow *platWin = newParentWindow->handle()) + install(static_cast<QWindowsWindow *>(platWin)); + else // Store for later creation, see menuBarOf() + newParentWindow->setProperty(menuBarPropertyName, qVariantFromValue<QObject *>(this)); +} + +QWindowsMenuBar *QWindowsMenuBar::menuBarOf(const QWindow *notYetCreatedWindow) +{ + const QVariant menuBarV = notYetCreatedWindow->property(menuBarPropertyName); + return menuBarV.canConvert<QObject *>() + ? qobject_cast<QWindowsMenuBar *>(menuBarV.value<QObject *>()) : nullptr; +} + +static inline void forceNcCalcSize(HWND hwnd) +{ + // Force WM_NCCALCSIZE to adjust margin: Does not appear to work? + SetWindowPos(hwnd, 0, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); +} + +void QWindowsMenuBar::install(QWindowsWindow *window) +{ + const HWND hwnd = window->handle(); + const BOOL result = SetMenu(hwnd, m_hMenuBar); + if (result) { + window->setMenuBar(this); + forceNcCalcSize(hwnd); + } +} + +void QWindowsMenuBar::removeFromWindow() +{ + if (QWindowsWindow *window = platformWindow()) { + const HWND hwnd = window->handle(); + SetMenu(hwnd, nullptr); + window->setMenuBar(nullptr); + forceNcCalcSize(hwnd); + } +} + +QPlatformMenu *QWindowsMenuBar::menuForTag(quintptr tag) const +{ + return traverseMenus(this, [tag] (const QWindowsMenu *m) { return m->tag() == tag; }); +} + +QPlatformMenu *QWindowsMenuBar::createMenu() const +{ + QPlatformMenu *result = new QWindowsMenu; + qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result; + return result; +} + +bool QWindowsMenuBar::notifyTriggered(uint id) +{ + QPlatformMenuItem *result = findMenuItemById(this, id); + if (result != nullptr) { + qCDebug(lcQpaMenus) << __FUNCTION__ << "id=" << id; + emit result->activated(); + } + return result != nullptr; +} + +bool QWindowsMenuBar::notifyAboutToShow(HMENU hmenu) +{ + if (QWindowsMenu *menu = findMenuByHandle(this, hmenu)) { + emit menu->aboutToShow(); + return true; + } + return false; +} + +QWindowsWindow *QWindowsMenuBar::platformWindow() const +{ + if (const QWindowsContext *ctx = QWindowsContext::instance()) { + if (QWindowsWindow *w = ctx->findPlatformWindow(this)) + return w; + } + return nullptr; +} + +void QWindowsMenuBar::redraw() const +{ + if (const QWindowsWindow *window = platformWindow()) + DrawMenuBar(window->handle()); +} + +#ifndef QT_NO_DEBUG_STREAM + +template <class M> /* Menu[Item] */ +static void formatTextSequence(QDebug &d, const QVector<M *> &v) +{ + if (const int size = v.size()) { + d << '[' << size << "]("; + for (int i = 0; i < size; ++i) { + if (i) + d << ", "; + if (!v.at(i)->isVisible()) + d << "[hidden] "; + d << '"' << v.at(i)->text() << '"'; + } + d << ')'; + } +} + +void QWindowsMenuItem::formatDebug(QDebug &d) const +{ + if (m_separator) + d << "separator, "; + else + d << '"' << m_text << "\", "; + d << static_cast<const void *>(this); + if (m_parentMenu) + d << ", parentMenu=" << static_cast<const void *>(m_parentMenu); + if (m_subMenu) + d << ", subMenu=" << static_cast<const void *>(m_subMenu); + d << ", tag=" << showbase << hex + << tag() << noshowbase << dec << ", id=" << m_id; + if (!m_shortcut.isEmpty()) + d << ", shortcut=" << m_shortcut; + if (m_visible) + d << " [visible]"; + if (m_enabled) + d << " [enabled]"; + if (m_checkable) + d << ", checked=" << m_checked; +} + +QDebug operator<<(QDebug d, const QPlatformMenuItem *i) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "QPlatformMenuItem("; + if (i) + static_cast<const QWindowsMenuItem *>(i)->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} + +void QWindowsMenu::formatDebug(QDebug &d) const +{ + d << '"' << m_text << "\", " << static_cast<const void *>(this) + << ", handle=" << m_hMenu; + if (m_parentMenuBar != nullptr) + d << " [on menubar]"; + if (m_parentMenu != nullptr) + d << " [on menu]"; + if (tag()) + d << ", tag=" << showbase << hex << tag() << noshowbase << dec; + if (m_visible) + d << " [visible]"; + if (m_enabled) + d << " [enabled]"; + d <<' '; + formatTextSequence(d, m_menuItems); +} + +void QWindowsMenuBar::formatDebug(QDebug &d) const +{ + d << static_cast<const void *>(this) << ' '; + formatTextSequence(d, m_menus); +} + +QDebug operator<<(QDebug d, const QPlatformMenu *m) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + if (m) { + d << m->metaObject()->className() << '('; + static_cast<const QWindowsMenu *>(m)->formatDebug(d); + d << ')'; + } else { + d << "QPlatformMenu(0)"; + } + return d; +} + +QDebug operator<<(QDebug d, const QPlatformMenuBar *mb) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "QPlatformMenuBar("; + if (mb) + static_cast<const QWindowsMenuBar *>(mb)->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} + +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsmenu.h b/src/plugins/platforms/windows/qwindowsmenu.h new file mode 100644 index 0000000000..d51a29676e --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsmenu.h @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSMENU_H +#define QWINDOWSMENU_H + +#include "qtwindowsglobal.h" + +#include <qpa/qplatformmenu.h> + +#include <QtCore/QVector> +#include <QtCore/QPair> + +QT_BEGIN_NAMESPACE + +class QDebug; + +class QWindowsMenu; +class QWindowsMenuBar; +class QWindowsWindow; + +class QWindowsMenuItem : public QPlatformMenuItem +{ + Q_OBJECT +public: + explicit QWindowsMenuItem(QWindowsMenu *parentMenu = nullptr); + ~QWindowsMenuItem(); + + void setText(const QString &text) override; + void setIcon(const QIcon &icon) override; + void setMenu(QPlatformMenu *menu) override; + void setVisible(bool isVisible) override; + void setIsSeparator(bool isSeparator) override; + void setFont(const QFont &) override {} + void setRole(MenuRole) override {} + void setCheckable(bool checkable) override; + void setChecked(bool isChecked) override; +#ifndef QT_NO_SHORTCUT + void setShortcut(const QKeySequence& shortcut) override; +#endif + void setEnabled(bool enabled) override; + void setIconSize(int size) override; + + const QWindowsMenu *parentMenu() const { return m_parentMenu; } + QWindowsMenu *parentMenu() { return m_parentMenu; } + HMENU parentMenuHandle() const; + QWindowsMenu *subMenu() const { return m_subMenu; } + UINT_PTR id() const { return m_id; } + void setId(uint id) { m_id = id; } + UINT state() const; + QString text() const { return m_text; } + QString nativeText() const; + bool isVisible() const { return m_visible; } + + void insertIntoMenu(QWindowsMenu *menuItem, bool append, int index); + bool removeFromMenu(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + void updateBitmap(); + void freeBitmap(); + void updateText(); + void insertIntoMenuHelper(QWindowsMenu *menu, bool append, int index); + + QWindowsMenu *m_parentMenu = nullptr; + QWindowsMenu *m_subMenu = nullptr; + UINT_PTR m_id; // Windows Id sent as wParam with WM_COMMAND or submenu handle. + QString m_text; + QIcon m_icon; + HBITMAP m_hbitmap = nullptr; + int m_iconSize = 0; + bool m_separator = false; + bool m_visible = true; + bool m_checkable = false; + bool m_checked = false; + bool m_enabled = true; + QKeySequence m_shortcut; +}; + +class QWindowsMenu : public QPlatformMenu +{ + Q_OBJECT +public: + typedef QVector<QWindowsMenuItem *> MenuItems; + + QWindowsMenu(); + ~QWindowsMenu(); + + void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; + void removeMenuItem(QPlatformMenuItem *menuItem) override; + void syncMenuItem(QPlatformMenuItem *) override {} + void syncSeparatorsCollapsible(bool) override {} + + void setText(const QString &text) override; + void setIcon(const QIcon &icon) override; + void setEnabled(bool enabled) override; + bool isEnabled() const override { return m_enabled; } + void setVisible(bool visible) override; + + QPlatformMenuItem *menuItemAt(int position) const override; + QPlatformMenuItem *menuItemForTag(quintptr tag) const override; + + QPlatformMenuItem *createMenuItem() const override; + QPlatformMenu *createSubMenu() const override; + + HMENU menuHandle() const { return m_hMenu; } + UINT_PTR id() const { return reinterpret_cast<UINT_PTR>(m_hMenu); } + QString text() const { return m_text; } + const MenuItems &menuItems() const { return m_menuItems; } + QWindowsMenuItem *itemForSubMenu(const QWindowsMenu *subMenu) const; + + const QWindowsMenuBar *parentMenuBar() const { return m_parentMenuBar; } + HMENU parentMenuBarHandle() const; + const QWindowsMenu *parentMenu() const { return m_parentMenu; } + void setAsItemSubMenu(QWindowsMenuItem *item); + void notifyRemoved(QWindowsMenuItem *item) { m_menuItems.removeOne(item); } + HMENU parentMenuHandle() const; + HMENU parentHandle() const; + bool isVisible() const { return m_visible; } + void insertIntoMenuBar(QWindowsMenuBar *bar, bool append, int index); + bool removeFromParent(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +protected: + explicit QWindowsMenu(QWindowsMenu *parentMenu, HMENU menu); + +private: + QWindowsMenuBar *m_parentMenuBar = nullptr; + QWindowsMenu *m_parentMenu = nullptr; + MenuItems m_menuItems; + HMENU m_hMenu = nullptr; + QString m_text; + QIcon m_icon; + bool m_visible = true; + bool m_enabled = true; +}; + +class QWindowsPopupMenu : public QWindowsMenu +{ + Q_OBJECT +public: + QWindowsPopupMenu(); + + static bool notifyTriggered(uint id); + static bool notifyAboutToShow(HMENU hmenu); + + void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) override; + void dismiss() override {} + + bool trackPopupMenu(HWND windowHandle, int x, int y); +}; + +class QWindowsMenuBar : public QPlatformMenuBar +{ + Q_OBJECT +public: + typedef QVector<QWindowsMenu *> Menus; + + QWindowsMenuBar(); + ~QWindowsMenuBar(); + + void insertMenu(QPlatformMenu *menu, QPlatformMenu *before) override; + void removeMenu(QPlatformMenu *menu) override; + void syncMenu(QPlatformMenu *) override {} + void handleReparent(QWindow *newParentWindow) override; + + QPlatformMenu *menuForTag(quintptr tag) const override; + QPlatformMenu *createMenu() const override; + + HMENU menuBarHandle() const { return m_hMenuBar; } + const Menus &menus() const { return m_menus; } + bool notifyTriggered(uint id); + bool notifyAboutToShow(HMENU hmenu); + void notifyRemoved(QWindowsMenu *menu) { m_menus.removeOne(menu); } + void redraw() const; + + void install(QWindowsWindow *window); + + static QWindowsMenuBar *menuBarOf(const QWindow *notYetCreatedWindow); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + QWindowsWindow *platformWindow() const; + void removeFromWindow(); + + Menus m_menus; + HMENU m_hMenuBar = nullptr; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QPlatformMenuItem *); +QDebug operator<<(QDebug d, const QPlatformMenu *); +QDebug operator<<(QDebug d, const QPlatformMenuBar *); +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE + +#endif // QWINDOWSMENU_H diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp index 9c9382c44c..34e6041687 100644 --- a/src/plugins/platforms/windows/qwindowsmime.cpp +++ b/src/plugins/platforms/windows/qwindowsmime.cpp @@ -41,6 +41,7 @@ #include "qwindowscontext.h" #include <QtGui/private/qdnd_p.h> +#include <QtCore/QByteArrayMatcher> #include <QtCore/QTextCodec> #include <QtCore/QMap> #include <QtCore/QUrl> @@ -51,6 +52,7 @@ #include <QtGui/QImageWriter> #include <shlobj.h> +#include <algorithm> QT_BEGIN_NAMESPACE @@ -955,9 +957,11 @@ QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pData QVariant result; if (canConvertToMime(mime, pDataObj)) { QByteArray html = getData(CF_HTML, pDataObj); + static Q_RELAXED_CONSTEXPR auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:"); + static Q_RELAXED_CONSTEXPR auto endMatcher = qMakeStaticByteArrayMatcher("EndHTML:"); qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html; - int start = html.indexOf("StartHTML:"); - int end = html.indexOf("EndHTML:"); + int start = startMatcher.indexIn(html); + int end = endMatcher.indexIn(html); if (start != -1) { int startOffset = start + 10; @@ -997,10 +1001,13 @@ bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeDa "StartFragment:0000000000\r\n" // 56-81 "EndFragment:0000000000\r\n\r\n"; // 82-107 - if (data.indexOf("<!--StartFragment-->") == -1) + static Q_RELAXED_CONSTEXPR auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->"); + static Q_RELAXED_CONSTEXPR auto endFragmentMatcher = qMakeStaticByteArrayMatcher("<!--EndFragment-->"); + + if (startFragmentMatcher.indexIn(data) == -1) result += "<!--StartFragment-->"; result += data; - if (data.indexOf("<!--EndFragment-->") == -1) + if (endFragmentMatcher.indexIn(data) == -1) result += "<!--EndFragment-->"; // set the correct number for EndHTML @@ -1008,9 +1015,9 @@ bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeDa memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length())); // set correct numbers for StartFragment and EndFragment - pos = QByteArray::number(result.indexOf("<!--StartFragment-->") + 20); + pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20); memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length())); - pos = QByteArray::number(result.indexOf("<!--EndFragment-->")); + pos = QByteArray::number(endFragmentMatcher.indexIn(result)); memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length())); return setData(result, pmedium); @@ -1258,15 +1265,16 @@ bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData QVector<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const { QVector<FORMATETC> formatetcs; - if (!outFormats.keys(mimeType).isEmpty() && mimeData->formats().contains(mimeType)) - formatetcs += setCf(outFormats.key(mimeType)); + const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType); + if (mit != outFormats.cend() && mimeData->formats().contains(mimeType)) + formatetcs += setCf(mit.key()); return formatetcs; } bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const { - return (!inFormats.keys(mimeType).isEmpty()) - && canGetData(inFormats.key(mimeType), pDataObj); + const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType); + return mit != inFormats.cend() && canGetData(mit.key(), pDataObj); } QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const @@ -1309,7 +1317,7 @@ public: QString mimeForFormat(const FORMATETC &formatetc) const override; private: - QMap<int, QString> formats; + mutable QMap<int, QString> formats; static QStringList ianaTypes; static QStringList excludeList; }; @@ -1374,15 +1382,13 @@ bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeDa QVector<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const { QVector<FORMATETC> formatetcs; - if (!formats.keys(mimeType).isEmpty()) { - formatetcs += setCf(formats.key(mimeType)); - } else if (!excludeList.contains(mimeType, Qt::CaseInsensitive)){ - // register any other available formats - int cf = QWindowsMime::registerMimeType(mimeType); - QLastResortMimes *that = const_cast<QLastResortMimes *>(this); - that->formats.insert(cf, mimeType); - formatetcs += setCf(cf); - } + auto mit = std::find(formats.begin(), formats.end(), mimeType); + // register any other available formats + if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive)) + mit = formats.insert(QWindowsMime::registerMimeType(mimeType), mimeType); + if (mit != formats.end()) + formatetcs += setCf(mit.key()); + if (!formatetcs.isEmpty()) qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs; return formatetcs; @@ -1420,14 +1426,11 @@ bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pD QString clipFormat = customMimeType(mimeType); const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16())); return canGetData(int(cf), pDataObj); - } else if (formats.keys(mimeType).isEmpty()) { - // if it is not in there then register it and see if we can get it - int cf = QWindowsMime::registerMimeType(mimeType); - return canGetData(cf, pDataObj); - } else { - return canGetData(formats.key(mimeType), pDataObj); } - return false; + // if it is not in there then register it and see if we can get it + const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType); + const int cf = mit != formats.cend() ? mit.key() : QWindowsMime::registerMimeType(mimeType); + return canGetData(cf, pDataObj); } QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const @@ -1441,11 +1444,10 @@ QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *p QString clipFormat = customMimeType(mimeType, &lindex); const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16())); data = getData(int(cf), pDataObj, lindex); - } else if (formats.keys(mimeType).isEmpty()) { - int cf = QWindowsMime::registerMimeType(mimeType); - data = getData(cf, pDataObj); } else { - data = getData(formats.key(mimeType), pDataObj); + const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType); + const int cf = mit != formats.cend() ? mit.key() : QWindowsMime::registerMimeType(mimeType); + data = getData(cf, pDataObj); } if (!data.isEmpty()) val = data; // it should be enough to return the data and let QMimeData do the rest. diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp index 0cabb66bca..9544fb81f7 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -60,7 +60,7 @@ * are present in the Windows SDK's, but not in older MSVC Express * versions. */ -#if defined(Q_CC_MINGW) || !defined(TOUCHEVENTF_MOVE) +#if !defined(TOUCHEVENTF_MOVE) typedef struct tagTOUCHINPUT { LONG x; @@ -85,7 +85,7 @@ typedef TOUCHINPUT const * PCTOUCHINPUT; # define TOUCHEVENTF_PALM 0x0080 # define TOUCHINPUTMASKF_CONTACTAREA 0x0004 # define TOUCHINPUTMASKF_EXTRAINFO 0x0002 -#endif // if defined(Q_CC_MINGW) || !defined(TOUCHEVENTF_MOVE) +#endif // if !defined(TOUCHEVENTF_MOVE) QT_BEGIN_NAMESPACE @@ -574,7 +574,8 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, - touchPoints); + touchPoints, + QWindowsKeyMapper::queryKeyboardModifiers()); return true; } diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp index d750eef19d..dc8e97c886 100644 --- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp +++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp @@ -64,7 +64,8 @@ enum ResourceType { HandleType, GlHandleType, GetDCType, - ReleaseDCType + ReleaseDCType, + VkSurface }; static int resourceType(const QByteArray &key) @@ -77,7 +78,8 @@ static int resourceType(const QByteArray &key) "handle", "glhandle", "getdc", - "releasedc" + "releasedc", + "vkSurface" }; const char ** const end = names + sizeof(names) / sizeof(names[0]); const char **result = std::find(names, end, key); @@ -112,6 +114,12 @@ void *QWindowsNativeInterface::nativeResourceForWindow(const QByteArray &resourc case QWindow::OpenGLSurface: case QWindow::OpenVGSurface: break; + case QWindow::VulkanSurface: +#if QT_CONFIG(vulkan) + if (type == VkSurface) + return bw->surface(nullptr, nullptr); // returns the address of the VkSurfaceKHR, not the value, as expected +#endif + break; } qWarning("%s: Invalid key '%s' requested.", __FUNCTION__, resource.constData()); return 0; diff --git a/src/plugins/platforms/windows/qwindowsole.cpp b/src/plugins/platforms/windows/qwindowsole.cpp index 9b71061aa5..0ceb0d82fa 100644 --- a/src/plugins/platforms/windows/qwindowsole.cpp +++ b/src/plugins/platforms/windows/qwindowsole.cpp @@ -99,39 +99,6 @@ DWORD QWindowsOleDataObject::reportedPerformedEffect() const return performedEffect; } -//--------------------------------------------------------------------- -// IUnknown Methods -//--------------------------------------------------------------------- - -STDMETHODIMP -QWindowsOleDataObject::QueryInterface(REFIID iid, void FAR* FAR* ppv) -{ - if (iid == IID_IUnknown || iid == IID_IDataObject) { - *ppv = this; - AddRef(); - return NOERROR; - } - *ppv = NULL; - return ResultFromScode(E_NOINTERFACE); -} - -STDMETHODIMP_(ULONG) -QWindowsOleDataObject::AddRef(void) -{ - return ++m_refs; -} - -STDMETHODIMP_(ULONG) -QWindowsOleDataObject::Release(void) -{ - if (--m_refs == 0) { - releaseQt(); - delete this; - return 0; - } - return m_refs; -} - STDMETHODIMP QWindowsOleDataObject::GetData(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium) { @@ -323,35 +290,6 @@ bool QWindowsOleEnumFmtEtc::isNull() const return m_isNull; } -// IUnknown methods -STDMETHODIMP -QWindowsOleEnumFmtEtc::QueryInterface(REFIID riid, void FAR* FAR* ppvObj) -{ - if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) { - *ppvObj = this; - AddRef(); - return NOERROR; - } - *ppvObj = NULL; - return ResultFromScode(E_NOINTERFACE); -} - -STDMETHODIMP_(ULONG) -QWindowsOleEnumFmtEtc::AddRef(void) -{ - return ++m_dwRefs; -} - -STDMETHODIMP_(ULONG) -QWindowsOleEnumFmtEtc::Release(void) -{ - if (--m_dwRefs == 0) { - delete this; - return 0; - } - return m_dwRefs; -} - // IEnumFORMATETC methods STDMETHODIMP QWindowsOleEnumFmtEtc::Next(ULONG celt, LPFORMATETC rgelt, ULONG FAR* pceltFetched) diff --git a/src/plugins/platforms/windows/qwindowsole.h b/src/plugins/platforms/windows/qwindowsole.h index 643011272b..fc58858f2c 100644 --- a/src/plugins/platforms/windows/qwindowsole.h +++ b/src/plugins/platforms/windows/qwindowsole.h @@ -40,6 +40,7 @@ #ifndef QWINDOWSOLE_H #define QWINDOWSOLE_H +#include "qwindowscombase.h" #include <QtCore/qt_windows.h> #include <QtCore/QMap> @@ -53,7 +54,7 @@ QT_BEGIN_NAMESPACE class QMimeData; class QWindow; -class QWindowsOleDataObject : public IDataObject +class QWindowsOleDataObject : public QWindowsComBase<IDataObject> { public: explicit QWindowsOleDataObject(QMimeData *mimeData); @@ -63,11 +64,6 @@ public: QMimeData *mimeData() const; DWORD reportedPerformedEffect() const; - // IUnknown methods - STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj); - STDMETHOD_(ULONG,AddRef)(void); - STDMETHOD_(ULONG,Release)(void); - // IDataObject methods STDMETHOD(GetData)(LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium); STDMETHOD(GetDataHere)(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium); @@ -82,13 +78,12 @@ public: STDMETHOD(EnumDAdvise)(LPENUMSTATDATA FAR* ppenumAdvise); private: - ULONG m_refs = 1; QPointer<QMimeData> data; const int CF_PERFORMEDDROPEFFECT; DWORD performedEffect = DROPEFFECT_NONE; }; -class QWindowsOleEnumFmtEtc : public IEnumFORMATETC +class QWindowsOleEnumFmtEtc : public QWindowsComBase<IEnumFORMATETC> { public: explicit QWindowsOleEnumFmtEtc(const QVector<FORMATETC> &fmtetcs); @@ -97,11 +92,6 @@ public: bool isNull() const; - // IUnknown methods - STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj); - STDMETHOD_(ULONG,AddRef)(void); - STDMETHOD_(ULONG,Release)(void); - // IEnumFORMATETC methods STDMETHOD(Next)(ULONG celt, LPFORMATETC rgelt, ULONG FAR* pceltFetched); STDMETHOD(Skip)(ULONG celt); @@ -111,7 +101,6 @@ public: private: bool copyFormatEtc(LPFORMATETC dest, const FORMATETC *src) const; - ULONG m_dwRefs = 1; ULONG m_nIndex = 0; QVector<LPFORMATETC> m_lpfmtetcs; bool m_isNull = false; diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp new file mode 100644 index 0000000000..901d132ea5 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#if defined(WINVER) && WINVER < 0x0601 +# undef WINVER +#endif +#if !defined(WINVER) +# define WINVER 0x0601 // required for NOTIFYICONDATA_V2_SIZE, ChangeWindowMessageFilterEx() (MinGW 5.3) +#endif + +#if defined(NTDDI_VERSION) && NTDDI_VERSION < 0x06010000 +# undef NTDDI_VERSION +#endif +#if !defined(NTDDI_VERSION) +# define NTDDI_VERSION 0x06010000 // required for Shell_NotifyIconGetRect (MinGW 5.3) +#endif + +#include "qwindowssystemtrayicon.h" +#include "qwindowscontext.h" +#include "qwindowstheme.h" +#include "qwindowsmenu.h" +#include "qwindowsscreen.h" + +#include <QtGui/qpixmap.h> +#include <QtCore/qdebug.h> +#include <QtCore/qrect.h> +#include <QtCore/qvector.h> +#include <QtCore/qsettings.h> + +#include <qt_windows.h> +#include <commctrl.h> +#include <shellapi.h> +#include <shlobj.h> +#include <windowsx.h> + +QT_BEGIN_NAMESPACE + +static const UINT q_uNOTIFYICONID = 0; + +static uint MYWM_TASKBARCREATED = 0; +#define MYWM_NOTIFYICON (WM_APP+101) + +Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &); + +// Copy QString data to a limited wchar_t array including \0. +static inline void qStringToLimitedWCharArray(QString in, wchar_t *target, int maxLength) +{ + const int length = qMin(maxLength - 1, in.size()); + if (length < in.size()) + in.truncate(length); + in.toWCharArray(target); + target[length] = wchar_t(0); +} + +static inline void initNotifyIconData(NOTIFYICONDATA &tnd) +{ + memset(&tnd, 0, sizeof(NOTIFYICONDATA)); + tnd.cbSize = sizeof(NOTIFYICONDATA); + tnd.uVersion = NOTIFYICON_VERSION_4; +} + +static void setIconContents(NOTIFYICONDATA &tnd, const QString &tip, HICON hIcon) +{ + tnd.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP; + tnd.uCallbackMessage = MYWM_NOTIFYICON; + tnd.hIcon = hIcon; + qStringToLimitedWCharArray(tip, tnd.szTip, sizeof(tnd.szTip) / sizeof(wchar_t)); +} + +// Match the HWND of the dummy window to the instances +struct QWindowsHwndSystemTrayIconEntry +{ + HWND hwnd; + QWindowsSystemTrayIcon *trayIcon; +}; + +typedef QVector<QWindowsHwndSystemTrayIconEntry> HwndTrayIconEntries; + +Q_GLOBAL_STATIC(HwndTrayIconEntries, hwndTrayIconEntries) + +static int indexOfHwnd(HWND hwnd) +{ + const HwndTrayIconEntries *entries = hwndTrayIconEntries(); + for (int i = 0, size = entries->size(); i < size; ++i) { + if (entries->at(i).hwnd == hwnd) + return i; + } + return -1; +} + +extern "C" LRESULT QT_WIN_CALLBACK qWindowsTrayIconWndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + if (message == MYWM_TASKBARCREATED || message == MYWM_NOTIFYICON + || message == WM_INITMENU || message == WM_INITMENUPOPUP + || message == WM_COMMAND) { + const int index = indexOfHwnd(hwnd); + if (index >= 0) { + MSG msg; + msg.hwnd = hwnd; // re-create MSG structure + msg.message = message; // time and pt fields ignored + msg.wParam = wParam; + msg.lParam = lParam; + msg.pt.x = GET_X_LPARAM(lParam); + msg.pt.y = GET_Y_LPARAM(lParam); + long result = 0; + if (hwndTrayIconEntries()->at(index).trayIcon->winEvent(msg, &result)) + return result; + } + } + return DefWindowProc(hwnd, message, wParam, lParam); +} + +// Note: Message windows (HWND_MESSAGE) are not sufficient, they +// will not receive the "TaskbarCreated" message. +static inline HWND createTrayIconMessageWindow() +{ + QWindowsContext *ctx = QWindowsContext::instance(); + if (!ctx) + return 0; + // Register window class in the platform plugin. + const QString className = + ctx->registerWindowClass(QStringLiteral("QTrayIconMessageWindowClass"), + qWindowsTrayIconWndProc); + const wchar_t windowName[] = L"QTrayIconMessageWindow"; + return CreateWindowEx(0, reinterpret_cast<const wchar_t *>(className.utf16()), + windowName, WS_OVERLAPPED, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, (HINSTANCE)GetModuleHandle(0), NULL); +} + +/*! + \class QWindowsSystemTrayIcon + \brief Windows native system tray icon + + \internal + \ingroup qt-lighthouse-win +*/ + +QWindowsSystemTrayIcon::QWindowsSystemTrayIcon() +{ + // For restoring the tray icon after explorer crashes + if (!MYWM_TASKBARCREATED) + MYWM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated"); + // Allow the WM_TASKBARCREATED message through the UIPI filter + ChangeWindowMessageFilterEx(m_hwnd, MYWM_TASKBARCREATED, MSGFLT_ALLOW, 0); + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "MYWM_TASKBARCREATED=" << MYWM_TASKBARCREATED; +} + +QWindowsSystemTrayIcon::~QWindowsSystemTrayIcon() +{ + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this; + ensureCleanup(); +} + +void QWindowsSystemTrayIcon::init() +{ + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this; + ensureInstalled(); +} + +void QWindowsSystemTrayIcon::cleanup() +{ + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this; + ensureCleanup(); +} + +void QWindowsSystemTrayIcon::updateIcon(const QIcon &icon) +{ + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this; + if (icon.cacheKey() == m_icon.cacheKey()) + return; + const HICON hIconToDestroy = createIcon(icon); + if (ensureInstalled()) + sendTrayMessage(NIM_MODIFY); + if (hIconToDestroy) + DestroyIcon(hIconToDestroy); +} + +void QWindowsSystemTrayIcon::updateToolTip(const QString &tooltip) +{ + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << tooltip << ')' << this; + if (m_toolTip == tooltip) + return; + m_toolTip = tooltip; + if (isInstalled()) + sendTrayMessage(NIM_MODIFY); +} + +QRect QWindowsSystemTrayIcon::geometry() const +{ + NOTIFYICONIDENTIFIER nid; + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(nid); + nid.hWnd = m_hwnd; + nid.uID = q_uNOTIFYICONID; + RECT rect; + const QRect result = SUCCEEDED(Shell_NotifyIconGetRect(&nid, &rect)) + ? QRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top) + : QRect(); + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "returns" << result; + return result; +} + +void QWindowsSystemTrayIcon::showMessage(const QString &title, const QString &messageIn, + const QIcon &icon, + QPlatformSystemTrayIcon::MessageIcon iconType, + int msecsIn) +{ + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << title << messageIn << icon + << iconType << msecsIn << ')' << this; + if (!supportsMessages()) + return; + // For empty messages, ensures that they show when only title is set + QString message = messageIn; + if (message.isEmpty() && !title.isEmpty()) + message.append(QLatin1Char(' ')); + + NOTIFYICONDATA tnd; + initNotifyIconData(tnd); + qStringToLimitedWCharArray(message, tnd.szInfo, 256); + qStringToLimitedWCharArray(title, tnd.szInfoTitle, 64); + + tnd.uID = q_uNOTIFYICONID; + tnd.dwInfoFlags = NIIF_USER; + + QSize size(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + const QSize largeIcon(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); + const QSize more = icon.actualSize(largeIcon); + if (more.height() > (largeIcon.height() * 3/4) || more.width() > (largeIcon.width() * 3/4)) { + tnd.dwInfoFlags |= NIIF_LARGE_ICON; + size = largeIcon; + } + QPixmap pm = icon.pixmap(size); + if (pm.isNull()) { + tnd.dwInfoFlags = NIIF_INFO; + } else { + if (pm.size() != size) { + qWarning("QSystemTrayIcon::showMessage: Wrong icon size (%dx%d), please add standard one: %dx%d", + pm.size().width(), pm.size().height(), size.width(), size.height()); + pm = pm.scaled(size, Qt::IgnoreAspectRatio); + } + tnd.hBalloonIcon = qt_pixmapToWinHICON(pm); + } + tnd.hWnd = m_hwnd; + tnd.uTimeout = msecsIn <= 0 ? UINT(10000) : UINT(msecsIn); // 10s default + tnd.uFlags = NIF_INFO | NIF_SHOWTIP; + + Shell_NotifyIcon(NIM_MODIFY, &tnd); +} + +bool QWindowsSystemTrayIcon::supportsMessages() const +{ + // The key does typically not exist on Windows 10, default to true. + return QWindowsContext::readAdvancedExplorerSettings(L"EnableBalloonTips", 1) != 0; +} + +QPlatformMenu *QWindowsSystemTrayIcon::createMenu() const +{ + if (QWindowsTheme::useNativeMenus() && m_menu.isNull()) + m_menu = new QWindowsPopupMenu; + qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "returns" << m_menu.data(); + return m_menu.data(); +} + +// Delay-install until an Icon exists +bool QWindowsSystemTrayIcon::ensureInstalled() +{ + if (isInstalled()) + return true; + if (m_hIcon == nullptr) + return false; + m_hwnd = createTrayIconMessageWindow(); + if (Q_UNLIKELY(m_hwnd == nullptr)) + return false; + QWindowsHwndSystemTrayIconEntry entry{m_hwnd, this}; + hwndTrayIconEntries()->append(entry); + sendTrayMessage(NIM_ADD); + return true; +} + +void QWindowsSystemTrayIcon::ensureCleanup() +{ + if (isInstalled()) { + const int index = indexOfHwnd(m_hwnd); + if (index >= 0) + hwndTrayIconEntries()->removeAt(index); + sendTrayMessage(NIM_DELETE); + DestroyWindow(m_hwnd); + m_hwnd = nullptr; + } + if (m_hIcon != nullptr) + DestroyIcon(m_hIcon); + m_hIcon = nullptr; + m_menu = nullptr; // externally owned + m_toolTip.clear(); +} + +bool QWindowsSystemTrayIcon::sendTrayMessage(DWORD msg) +{ + NOTIFYICONDATA tnd; + initNotifyIconData(tnd); + tnd.uID = q_uNOTIFYICONID; + tnd.hWnd = m_hwnd; + tnd.uFlags = NIF_SHOWTIP; + if (msg == NIM_ADD || msg == NIM_MODIFY) + setIconContents(tnd, m_toolTip, m_hIcon); + if (!Shell_NotifyIcon(msg, &tnd)) + return false; + return msg != NIM_ADD || Shell_NotifyIcon(NIM_SETVERSION, &tnd); +} + +// Return the old icon to be freed after modifying the tray icon. +HICON QWindowsSystemTrayIcon::createIcon(const QIcon &icon) +{ + const HICON oldIcon = m_hIcon; + m_hIcon = nullptr; + if (icon.isNull()) + return oldIcon; + const QSize requestedSize = QSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + const QSize size = icon.actualSize(requestedSize); + const QPixmap pm = icon.pixmap(size); + if (!pm.isNull()) + m_hIcon = qt_pixmapToWinHICON(pm); + return oldIcon; +} + +bool QWindowsSystemTrayIcon::winEvent(const MSG &message, long *result) +{ + *result = 0; + switch (message.message) { + case MYWM_NOTIFYICON: { + Q_ASSERT(q_uNOTIFYICONID == HIWORD(message.lParam)); + const int trayMessage = LOWORD(message.lParam); + switch (trayMessage) { + case NIN_SELECT: + case NIN_KEYSELECT: + if (m_ignoreNextMouseRelease) + m_ignoreNextMouseRelease = false; + else + emit activated(Trigger); + break; + case WM_LBUTTONDBLCLK: + m_ignoreNextMouseRelease = true; // Since DBLCLICK Generates a second mouse + emit activated(DoubleClick); // release we must ignore it + break; + case WM_CONTEXTMENU: { + const QPoint globalPos = QPoint(GET_X_LPARAM(message.wParam), GET_Y_LPARAM(message.wParam)); + const QPlatformScreen *screen = QWindowsContext::instance()->screenManager().screenAtDp(globalPos); + emit contextMenuRequested(globalPos, screen); + emit activated(Context); + if (m_menu) + m_menu->trackPopupMenu(message.hwnd, globalPos.x(), globalPos.y()); + } + break; + case NIN_BALLOONUSERCLICK: + emit messageClicked(); + break; + case WM_MBUTTONUP: + emit activated(MiddleClick); + break; + default: + break; + } + } + break; + case WM_INITMENU: + case WM_INITMENUPOPUP: + QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(message.wParam)); + break; + case WM_COMMAND: + QWindowsPopupMenu::notifyTriggered(LOWORD(message.wParam)); + break; + default: + if (message.message == MYWM_TASKBARCREATED) // self-registered message id (tray crashed) + sendTrayMessage(NIM_ADD); + break; + } + return false; +} + +#ifndef QT_NO_DEBUG_STREAM + +void QWindowsSystemTrayIcon::formatDebug(QDebug &d) const +{ + d << static_cast<const void *>(this) << ", \"" << m_toolTip + << "\", hwnd=" << m_hwnd << ", m_hIcon=" << m_hIcon << ", menu=" + << m_menu.data(); +} + +QDebug operator<<(QDebug d, const QWindowsSystemTrayIcon *t) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "QWindowsSystemTrayIcon("; + if (t) + t->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.h b/src/plugins/platforms/windows/qwindowssystemtrayicon.h new file mode 100644 index 0000000000..1f696180cd --- /dev/null +++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSSYSTEMTRAYICON_H +#define QWINDOWSSYSTEMTRAYICON_H + +#include <QtGui/qicon.h> +#include <QtGui/qpa/qplatformsystemtrayicon.h> + +#include <QtCore/qpointer.h> +#include <QtCore/qstring.h> +#include <QtCore/qt_windows.h> + +QT_BEGIN_NAMESPACE + +class QDebug; + +class QWindowsPopupMenu; + +class QWindowsSystemTrayIcon : public QPlatformSystemTrayIcon +{ +public: + QWindowsSystemTrayIcon(); + ~QWindowsSystemTrayIcon(); + + void init() override; + void cleanup() override; + void updateIcon(const QIcon &icon) override; + void updateToolTip(const QString &tooltip) override; + void updateMenu(QPlatformMenu *) override {} + QRect geometry() const override; + void showMessage(const QString &title, const QString &msg, + const QIcon &icon, MessageIcon iconType, int msecs) override; + + bool isSystemTrayAvailable() const override { return true; } + bool supportsMessages() const override; + + QPlatformMenu *createMenu() const override; + + bool winEvent(const MSG &message, long *result); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + bool isInstalled() const { return m_hwnd != nullptr; } + bool ensureInstalled(); + void ensureCleanup(); + bool sendTrayMessage(DWORD msg); + HICON createIcon(const QIcon &icon); + + QIcon m_icon; + QString m_toolTip; + HWND m_hwnd = nullptr; + HICON m_hIcon = nullptr; + mutable QPointer<QWindowsPopupMenu> m_menu; + bool m_ignoreNextMouseRelease = false; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QWindowsSystemTrayIcon *); +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE + +#endif // QWINDOWSSYSTEMTRAYICON_H diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp index 5fe58fbfa5..5fdc664603 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -355,6 +355,8 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L if (!LOWORD(lParam)) { qCDebug(lcQpaTablet) << "leave proximity for device #" << m_currentDevice; + if (m_currentDevice < 0 || m_currentDevice >= m_devices.size()) // QTBUG-65120, spurious leave observed + return false; if (totalPacks > 0) { QWindowSystemInterface::handleTabletLeaveProximityEvent(proximityBuffer[0].pkTime, m_devices.at(m_currentDevice).currentDevice, @@ -473,13 +475,13 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() // Z = sin(altitude) // X Tilt = arctan(X / Z) // Y Tilt = arctan(Y / Z) - const double radAzim = (packet.pkOrientation.orAzimuth / 10.0) * (M_PI / 180); - const double tanAlt = std::tan((std::abs(packet.pkOrientation.orAltitude / 10.0)) * (M_PI / 180)); + const double radAzim = qDegreesToRadians(packet.pkOrientation.orAzimuth / 10.0); + const double tanAlt = std::tan(qDegreesToRadians(std::abs(packet.pkOrientation.orAltitude / 10.0))); - const double degX = std::atan(std::sin(radAzim) / tanAlt); - const double degY = std::atan(std::cos(radAzim) / tanAlt); - tiltX = int(degX * (180 / M_PI)); - tiltY = int(-degY * (180 / M_PI)); + const double radX = std::atan(std::sin(radAzim) / tanAlt); + const double radY = std::atan(std::cos(radAzim) / tanAlt); + tiltX = int(qRadiansToDegrees(radX)); + tiltY = int(qRadiansToDegrees(-radY)); rotation = 360.0 - (packet.pkOrientation.orTwist / 10.0); if (rotation > 180.0) rotation -= 360.0; diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index 7916211219..651c661d6b 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -38,15 +38,19 @@ ****************************************************************************/ // SHSTOCKICONINFO is only available since Vista -#if _WIN32_WINNT < 0x0600 +#if _WIN32_WINNT < 0x0601 # undef _WIN32_WINNT -# define _WIN32_WINNT 0x0600 +# define _WIN32_WINNT 0x0601 #endif #include "qwindowstheme.h" +#include "qwindowsmenu.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" #include "qwindowsintegration.h" +#if QT_CONFIG(systemtrayicon) +# include "qwindowssystemtrayicon.h" +#endif #include "qt_windows.h" #include <commctrl.h> #include <objbase.h> @@ -415,13 +419,7 @@ static inline QStringList iconThemeSearchPaths() static inline QStringList styleNames() { - QStringList result; - if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) - result.append(QStringLiteral("WindowsVista")); - if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP) - result.append(QStringLiteral("WindowsXP")); - result.append(QStringLiteral("Windows")); - return result; + return { QStringLiteral("WindowsVista"), QStringLiteral("Windows") }; } static inline int uiEffects() @@ -554,6 +552,13 @@ QPlatformDialogHelper *QWindowsTheme::createPlatformDialogHelper(DialogType type return QWindowsDialogs::createHelper(type); } +#if QT_CONFIG(systemtrayicon) +QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const +{ + return new QWindowsSystemTrayIcon; +} +#endif + void QWindowsTheme::windowsThemeChanged(QWindow * window) { refresh(); @@ -922,4 +927,55 @@ QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOpt return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions)); } +static inline bool doUseNativeMenus() +{ + const unsigned options = QWindowsIntegration::instance()->options(); + if ((options & QWindowsIntegration::NoNativeMenus) != 0) + return false; + if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0) + return true; + // "Auto" mode: For non-widget or Quick Controls 2 applications + if (!QCoreApplication::instance()->inherits("QApplication")) + return true; + const QWindowList &topLevels = QGuiApplication::topLevelWindows(); + for (const QWindow *t : topLevels) { + if (t->inherits("QQuickApplicationWindow")) + return true; + } + return false; +} + +bool QWindowsTheme::useNativeMenus() +{ + static const bool result = doUseNativeMenus(); + return result; +} + +QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const +{ + qCDebug(lcQpaMenus) << __FUNCTION__; + return QWindowsTheme::useNativeMenus() ? new QWindowsMenuItem : nullptr; +} + +QPlatformMenu *QWindowsTheme::createPlatformMenu() const +{ + qCDebug(lcQpaMenus) << __FUNCTION__; + // We create a popup menu here, since it will likely be used as context + // menu. Submenus should be created the factory functions of + // QPlatformMenu/Bar. Note though that Quick Controls 1 will use this + // function for submenus as well, but this has been found to work. + return QWindowsTheme::useNativeMenus() ? new QWindowsPopupMenu : nullptr; +} + +QPlatformMenuBar *QWindowsTheme::createPlatformMenuBar() const +{ + qCDebug(lcQpaMenus) << __FUNCTION__; + return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr; +} + +void QWindowsTheme::showPlatformMenuBar() +{ + qCDebug(lcQpaMenus) << __FUNCTION__; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index a3019ff6eb..237e8158fa 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -59,6 +59,9 @@ public: bool usePlatformNativeDialog(DialogType type) const override; QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; +#if QT_CONFIG(systemtrayicon) + QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; +#endif QVariant themeHint(ThemeHint) const override; const QPalette *palette(Palette type = SystemPalette) const override { return m_palettes[type]; } @@ -74,6 +77,13 @@ public: QList<QSize> availableFileIconSizes() const { return m_fileIconSizes; } + QPlatformMenuItem *createPlatformMenuItem() const override; + QPlatformMenu *createPlatformMenu() const override; + QPlatformMenuBar *createPlatformMenuBar() const override; + void showPlatformMenuBar() override; + + static bool useNativeMenus(); + static const char *name; private: diff --git a/src/plugins/platforms/windows/qwindowsvulkaninstance.cpp b/src/plugins/platforms/windows/qwindowsvulkaninstance.cpp new file mode 100644 index 0000000000..d81ee8ba29 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsvulkaninstance.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#include "qwindowsvulkaninstance.h" + +QT_BEGIN_NAMESPACE + +QWindowsVulkanInstance::QWindowsVulkanInstance(QVulkanInstance *instance) + : m_instance(instance), + m_getPhysDevPresSupport(nullptr), + m_createSurface(nullptr), + m_destroySurface(nullptr) +{ + if (qEnvironmentVariableIsSet("QT_VULKAN_LIB")) + m_lib.setFileName(QString::fromUtf8(qgetenv("QT_VULKAN_LIB"))); + else + m_lib.setFileName(QStringLiteral("vulkan-1")); + + if (!m_lib.load()) { + qWarning("Failed to load %s: %s", qPrintable(m_lib.fileName()), qPrintable(m_lib.errorString())); + return; + } + + init(&m_lib); +} + +void QWindowsVulkanInstance::createOrAdoptInstance() +{ + initInstance(m_instance, QByteArrayList() << QByteArrayLiteral("VK_KHR_win32_surface")); + + if (!m_vkInst) + return; + + m_getPhysDevPresSupport = reinterpret_cast<PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR>( + m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceWin32PresentationSupportKHR")); + if (!m_getPhysDevPresSupport) + qWarning("Failed to find vkGetPhysicalDeviceWin32PresentationSupportKHR"); +} + +QWindowsVulkanInstance::~QWindowsVulkanInstance() +{ +} + +bool QWindowsVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + QWindow *window) +{ + if (!m_getPhysDevPresSupport || !m_getPhysDevSurfaceSupport) + return true; + + bool ok = m_getPhysDevPresSupport(physicalDevice, queueFamilyIndex); + + VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window); + VkBool32 supported = false; + m_getPhysDevSurfaceSupport(physicalDevice, queueFamilyIndex, surface, &supported); + ok &= bool(supported); + + return ok; +} + +VkSurfaceKHR QWindowsVulkanInstance::createSurface(HWND win) +{ + VkSurfaceKHR surface = 0; + + if (!m_createSurface) { + m_createSurface = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>( + m_vkGetInstanceProcAddr(m_vkInst, "vkCreateWin32SurfaceKHR")); + } + if (!m_createSurface) { + qWarning("Failed to find vkCreateWin32SurfaceKHR"); + return surface; + } + if (!m_destroySurface) { + m_destroySurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>( + m_vkGetInstanceProcAddr(m_vkInst, "vkDestroySurfaceKHR")); + } + if (!m_destroySurface) { + qWarning("Failed to find vkDestroySurfaceKHR"); + return surface; + } + + VkWin32SurfaceCreateInfoKHR surfaceInfo; + memset(&surfaceInfo, 0, sizeof(surfaceInfo)); + surfaceInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + surfaceInfo.hinstance = GetModuleHandle(nullptr); + surfaceInfo.hwnd = win; + VkResult err = m_createSurface(m_vkInst, &surfaceInfo, nullptr, &surface); + if (err != VK_SUCCESS) + qWarning("Failed to create Vulkan surface: %d", err); + + return surface; +} + +void QWindowsVulkanInstance::destroySurface(VkSurfaceKHR surface) +{ + if (m_destroySurface && surface) + m_destroySurface(m_vkInst, surface, nullptr); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsvulkaninstance.h b/src/plugins/platforms/windows/qwindowsvulkaninstance.h new file mode 100644 index 0000000000..ca60ab7627 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsvulkaninstance.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSVULKANINSTANCE_H +#define QWINDOWSVULKANINSTANCE_H + +#if defined(VULKAN_H_) && !defined(VK_USE_PLATFORM_WIN32_KHR) +#error "vulkan.h included without Win32 WSI" +#endif + +#define VK_USE_PLATFORM_WIN32_KHR + +#include <QtVulkanSupport/private/qbasicvulkanplatforminstance_p.h> +#include <QLibrary> + +QT_BEGIN_NAMESPACE + +class QWindowsVulkanInstance : public QBasicPlatformVulkanInstance +{ +public: + QWindowsVulkanInstance(QVulkanInstance *instance); + ~QWindowsVulkanInstance(); + + void createOrAdoptInstance() override; + bool supportsPresent(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, QWindow *window) override; + + VkSurfaceKHR createSurface(HWND win); + void destroySurface(VkSurfaceKHR surface); + +private: + QVulkanInstance *m_instance; + QLibrary m_lib; + PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR m_getPhysDevPresSupport; + PFN_vkCreateWin32SurfaceKHR m_createSurface; + PFN_vkDestroySurfaceKHR m_destroySurface; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSVULKANINSTANCE_H diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 402009c70d..1d81fa9cd5 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -44,6 +44,7 @@ #endif #include "qwindowsscreen.h" #include "qwindowsintegration.h" +#include "qwindowsmenu.h" #include "qwindowsnativeinterface.h" #if QT_CONFIG(dynamicgl) # include "qwindowsglcontext.h" @@ -70,8 +71,14 @@ #include <dwmapi.h> +#if QT_CONFIG(vulkan) +#include "qwindowsvulkaninstance.h" +#endif + QT_BEGIN_NAMESPACE +typedef QSharedPointer<QWindowCreationContext> QWindowCreationContextPtr; + enum { defaultWindowWidth = 160, defaultWindowHeight = 160 @@ -227,6 +234,23 @@ QDebug operator<<(QDebug d, const WINDOWPLACEMENT &wp) << ", rcNormalPosition=" << wp.rcNormalPosition; return d; } + +QDebug operator<<(QDebug d, const GUID &guid) +{ + QDebugStateSaver saver(d); + d.nospace(); + d << '{' << hex << uppercasedigits << qSetPadChar(QLatin1Char('0')) + << qSetFieldWidth(8) << guid.Data1 + << qSetFieldWidth(0) << '-' << qSetFieldWidth(4) + << guid.Data2 << qSetFieldWidth(0) << '-' << qSetFieldWidth(4) + << guid.Data3 << qSetFieldWidth(0) << '-' << qSetFieldWidth(4) + << qSetFieldWidth(2) << guid.Data4[0] << guid.Data4[1] + << qSetFieldWidth(0) << '-' << qSetFieldWidth(2); + for (int i = 2; i < 8; ++i) + d << guid.Data4[i]; + d << qSetFieldWidth(0) << '}'; + return d; +} #endif // !QT_NO_DEBUG_STREAM // QTBUG-43872, for windows that do not have WS_EX_TOOLWINDOW set, WINDOWPLACEMENT @@ -294,13 +318,15 @@ static QWindow::Visibility windowVisibility_sys(HWND hwnd) return QWindow::Windowed; } -static inline bool windowIsOpenGL(const QWindow *w) +static inline bool windowIsAccelerated(const QWindow *w) { switch (w->surfaceType()) { case QSurface::OpenGLSurface: return true; case QSurface::RasterGLSurface: return qt_window_private(const_cast<QWindow *>(w))->compositing; + case QSurface::VulkanSurface: + return true; default: return false; } @@ -361,11 +387,11 @@ bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool has return needsLayered; } -static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bool openGL, qreal level) +static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bool accelerated, qreal level) { if (QWindowsWindow::setWindowLayered(hwnd, flags, hasAlpha, level)) { const BYTE alpha = BYTE(qRound(255.0 * level)); - if (hasAlpha && !openGL && (flags & Qt::FramelessWindowHint)) { + if (hasAlpha && !accelerated && (flags & Qt::FramelessWindowHint)) { // Non-GL windows with alpha: Use blend function to update. BLENDFUNCTION blend = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; UpdateLayeredWindow(hwnd, NULL, NULL, NULL, NULL, NULL, 0, &blend, ULW_ALPHA); @@ -379,13 +405,13 @@ static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bo static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::WindowFlags flags, qreal opacity) { - const bool isGL = windowIsOpenGL(w); + const bool isAccelerated = windowIsAccelerated(w); const bool hasAlpha = w->format().hasAlpha(); - if (isGL && hasAlpha) + if (isAccelerated && hasAlpha) applyBlurBehindWindow(hwnd); - setWindowOpacity(hwnd, flags, hasAlpha, isGL, opacity); + setWindowOpacity(hwnd, flags, hasAlpha, isAccelerated, opacity); } /*! @@ -598,8 +624,6 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag QWindowsWindowData WindowCreationData::create(const QWindow *w, const WindowData &data, QString title) const { - typedef QSharedPointer<QWindowCreationContext> QWindowCreationContextPtr; - WindowData result; result.flags = flags; @@ -617,7 +641,7 @@ QWindowsWindowData // Capture events before CreateWindowEx() returns. The context is cleared in // the QWindowsWindow constructor. - const QWindowCreationContextPtr context(new QWindowCreationContext(w, rect, data.customMargins, style, exStyle)); + const QWindowCreationContextPtr context(new QWindowCreationContext(w, data.geometry, rect, data.customMargins, style, exStyle)); QWindowsContext::instance()->setWindowCreationContext(context); qCDebug(lcQpaWindows).nospace() @@ -672,7 +696,7 @@ void WindowCreationData::initialize(const QWindow *w, HWND hwnd, bool frameChang { if (!hwnd) return; - UINT swpFlags = SWP_NOMOVE | SWP_NOSIZE; + UINT swpFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER; if (frameChange) swpFlags |= SWP_FRAMECHANGED; if (topLevel) { @@ -985,10 +1009,11 @@ void QWindowsForeignWindow::setVisible(bool visible) */ QWindowCreationContext::QWindowCreationContext(const QWindow *w, - const QRect &geometry, + const QRect &geometryIn, const QRect &geometry, const QMargins &cm, DWORD style_, DWORD exStyle_) : geometryHint(w, cm), window(w), style(style_), exStyle(exStyle_), + requestedGeometryIn(geometryIn), requestedGeometry(geometry), obtainedGeometry(geometry), margins(QWindowsGeometryHint::frame(style, exStyle)), customMargins(cm) { @@ -1002,6 +1027,8 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, const QMargins effectiveMargins = margins + customMargins; frameWidth = effectiveMargins.left() + geometry.width() + effectiveMargins.right(); frameHeight = effectiveMargins.top() + geometry.height() + effectiveMargins.bottom(); + if (QWindowsMenuBar::menuBarOf(w) != nullptr) + frameHeight += GetSystemMetrics(SM_CYMENU); const bool isDefaultPosition = !frameX && !frameY && w->isTopLevel(); if (!QWindowsGeometryHint::positionIncludesFrame(w) && !isDefaultPosition) { frameX -= effectiveMargins.left(); @@ -1047,9 +1074,10 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) m_data(data), m_cursor(new CursorHandle), m_format(aWindow->requestedFormat()) +#if QT_CONFIG(vulkan) + , m_vkSurface(0) +#endif { - // Clear the creation context as the window can be found in QWindowsContext's map. - QWindowsContext::instance()->setWindowCreationContext(QSharedPointer<QWindowCreationContext>()); QWindowsContext::instance()->addWindow(m_data.hwnd, this); const Qt::WindowType type = aWindow->type(); if (type == Qt::Desktop) @@ -1062,13 +1090,20 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) setFlag(OpenGL_ES2); } #endif // QT_NO_OPENGL +#if QT_CONFIG(vulkan) + if (aWindow->surfaceType() == QSurface::VulkanSurface) + setFlag(VulkanSurface); +#endif updateDropSite(window()->isTopLevel()); registerTouchWindow(); - setWindowState(aWindow->windowState()); + setWindowState(aWindow->windowStates()); const qreal opacity = qt_window_private(aWindow)->opacity; if (!qFuzzyCompare(opacity, qreal(1.0))) setOpacity(opacity); + + setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window())); + if (aWindow->isTopLevel()) setWindowIcon(aWindow->icon()); clearFlag(WithinCreate); @@ -1083,6 +1118,27 @@ QWindowsWindow::~QWindowsWindow() destroyIcon(); } +void QWindowsWindow::initialize() +{ + // Clear the creation context as the window can be found in QWindowsContext's map. + QWindowCreationContextPtr creationContext = + QWindowsContext::instance()->setWindowCreationContext(QWindowCreationContextPtr()); + + // Trigger geometry change (unless it has a special state in which case setWindowState() + // will send the message) and screen change signals of QWindow. + QWindow *w = window(); + if (w->type() != Qt::Desktop) { + const Qt::WindowState state = w->windowState(); + if (state != Qt::WindowMaximized && state != Qt::WindowFullScreen + && creationContext->requestedGeometryIn != creationContext->obtainedGeometry) { + QWindowSystemInterface::handleGeometryChange(w, creationContext->obtainedGeometry); + } + QPlatformScreen *obtainedScreen = screenForGeometry(creationContext->obtainedGeometry); + if (obtainedScreen && screen() != obtainedScreen) + QWindowSystemInterface::handleWindowScreenChanged(w, obtainedScreen->screen()); + } +} + void QWindowsWindow::fireExpose(const QRegion ®ion, bool force) { if (region.isEmpty() && !force) @@ -1115,6 +1171,14 @@ void QWindowsWindow::destroyWindow() if (hasMouseCapture()) setMouseGrabEnabled(false); setDropSiteEnabled(false); +#if QT_CONFIG(vulkan) + if (m_vkSurface) { + QVulkanInstance *inst = window()->vulkanInstance(); + if (inst) + static_cast<QWindowsVulkanInstance *>(inst->handle())->destroySurface(m_vkSurface); + m_vkSurface = 0; + } +#endif #ifndef QT_NO_OPENGL if (m_surface) { if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) @@ -1332,20 +1396,48 @@ static inline bool testShowWithoutActivating(const QWindow *window) return showWithoutActivating.isValid() && showWithoutActivating.toBool(); } +static void setMinimizedGeometry(HWND hwnd, const QRect &r) +{ + WINDOWPLACEMENT windowPlacement; + windowPlacement.length = sizeof(WINDOWPLACEMENT); + if (GetWindowPlacement(hwnd, &windowPlacement)) { + windowPlacement.showCmd = SW_SHOWMINIMIZED; + windowPlacement.rcNormalPosition = RECTfromQRect(r); + SetWindowPlacement(hwnd, &windowPlacement); + } +} + +static void setRestoreMaximizedFlag(HWND hwnd, bool set = true) +{ + // Let Windows know that we need to restore as maximized + WINDOWPLACEMENT windowPlacement; + windowPlacement.length = sizeof(WINDOWPLACEMENT); + if (GetWindowPlacement(hwnd, &windowPlacement)) { + if (set) + windowPlacement.flags |= WPF_RESTORETOMAXIMIZED; + else + windowPlacement.flags &= ~WPF_RESTORETOMAXIMIZED; + SetWindowPlacement(hwnd, &windowPlacement); + } +} + // partially from QWidgetPrivate::show_sys() void QWindowsWindow::show_sys() const { int sm = SW_SHOWNORMAL; bool fakedMaximize = false; + bool restoreMaximize = false; const QWindow *w = window(); const Qt::WindowFlags flags = w->flags(); const Qt::WindowType type = w->type(); if (w->isTopLevel()) { - const Qt::WindowState state = w->windowState(); + const Qt::WindowStates state = w->windowStates(); if (state & Qt::WindowMinimized) { sm = SW_SHOWMINIMIZED; if (!isVisible()) sm = SW_SHOWMINNOACTIVE; + if (state & Qt::WindowMaximized) + restoreMaximize = true; } else { updateTransientParent(); if (state & Qt::WindowMaximized) { @@ -1366,7 +1458,7 @@ void QWindowsWindow::show_sys() const if (type == Qt::Popup || type == Qt::ToolTip || type == Qt::Tool || testShowWithoutActivating(w)) sm = SW_SHOWNOACTIVATE; - if (w->windowState() & Qt::WindowMaximized) + if (w->windowStates() & Qt::WindowMaximized) setFlag(WithinMaximize); // QTBUG-8361 ShowWindow(m_data.hwnd, sm); @@ -1379,6 +1471,8 @@ void QWindowsWindow::show_sys() const SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } + if (restoreMaximize) + setRestoreMaximizedFlag(m_data.hwnd); } void QWindowsWindow::setParent(const QPlatformWindow *newParent) @@ -1434,8 +1528,10 @@ void QWindowsWindow::handleHidden() void QWindowsWindow::handleCompositionSettingsChanged() { const QWindow *w = window(); - if (w->surfaceType() == QWindow::OpenGLSurface && w->format().hasAlpha()) + if ((w->surfaceType() == QWindow::OpenGLSurface || w->surfaceType() == QWindow::VulkanSurface) + && w->format().hasAlpha()) { applyBlurBehindWindow(handle()); + } } static QRect normalFrameGeometry(HWND hwnd) @@ -1452,7 +1548,8 @@ static QRect normalFrameGeometry(HWND hwnd) QRect QWindowsWindow::normalGeometry() const { // Check for fake 'fullscreen' mode. - const bool fakeFullScreen = m_savedFrameGeometry.isValid() && window()->windowState() == Qt::WindowFullScreen; + const bool fakeFullScreen = + m_savedFrameGeometry.isValid() && (window()->windowStates() & Qt::WindowFullScreen); const QRect frame = fakeFullScreen ? m_savedFrameGeometry : normalFrameGeometry(m_data.hwnd); const QMargins margins = fakeFullScreen ? QWindowsGeometryHint::frame(m_savedStyle, 0) : frameMargins(); return frame.isValid() ? frame.marginsRemoved(margins) : frame; @@ -1467,13 +1564,15 @@ void QWindowsWindow::setGeometry(const QRect &rectIn) const QMargins margins = frameMargins(); rect.moveTopLeft(rect.topLeft() + QPoint(margins.left(), margins.top())); } - if (m_windowState == Qt::WindowMinimized) + if (m_windowState & Qt::WindowMinimized) m_data.geometry = rect; // Otherwise set by handleGeometryChange() triggered by event. if (m_data.hwnd) { // A ResizeEvent with resulting geometry will be sent. If we cannot // achieve that size (for example, window title minimal constraint), // notify and warn. + setFlag(WithinSetGeometry); setGeometry_sys(rect); + clearFlag(WithinSetGeometry); if (m_data.geometry != rect && (isVisible() || QLibraryInfo::isDebugBuild())) { qWarning("%s: Unable to set geometry %dx%d+%d+%d on %s/'%s'." " Resulting geometry: %dx%d+%d+%d " @@ -1510,18 +1609,21 @@ void QWindowsWindow::handleResized(int wParam) case SIZE_MAXSHOW: return; case SIZE_MINIMIZED: // QTBUG-53577, prevent state change events during programmatic state change - if (!testFlag(WithinSetStyle)) - handleWindowStateChange(Qt::WindowMinimized); + if (!testFlag(WithinSetStyle) && !testFlag(WithinSetGeometry)) + handleWindowStateChange(m_windowState | Qt::WindowMinimized); return; case SIZE_MAXIMIZED: - if (!testFlag(WithinSetStyle)) - handleWindowStateChange(Qt::WindowMaximized); + if (!testFlag(WithinSetStyle) && !testFlag(WithinSetGeometry)) + handleWindowStateChange(Qt::WindowMaximized | (isFullScreen_sys() ? Qt::WindowFullScreen + : Qt::WindowNoState)); handleGeometryChange(); break; case SIZE_RESTORED: - if (!testFlag(WithinSetStyle)) { + if (!testFlag(WithinSetStyle) && !testFlag(WithinSetGeometry)) { if (isFullScreen_sys()) - handleWindowStateChange(Qt::WindowFullScreen); + handleWindowStateChange( + Qt::WindowFullScreen + | (testFlag(MaximizeToFullScreen) ? Qt::WindowMaximized : Qt::WindowNoState)); else if (m_windowState != Qt::WindowNoState && !testFlag(MaximizeToFullScreen)) handleWindowStateChange(Qt::WindowNoState); } @@ -1554,7 +1656,6 @@ void QWindowsWindow::handleGeometryChange() { const QRect previousGeometry = m_data.geometry; m_data.geometry = geometry_sys(); - QPlatformWindow::setGeometry(m_data.geometry); QWindowSystemInterface::handleGeometryChange(window(), m_data.geometry); // QTBUG-32121: OpenGL/normal windows (with exception of ANGLE) do not receive // expose events when shrinking, synthesize. @@ -1668,8 +1769,11 @@ bool QWindowsWindow::handleWmPaint(HWND hwnd, UINT message, BeginPaint(hwnd, &ps); // Observed painting problems with Aero style disabled (QTBUG-7865). - if (Q_UNLIKELY(testFlag(OpenGLSurface) && testFlag(OpenGLDoubleBuffered) && !dwmIsCompositionEnabled())) + if (Q_UNLIKELY(!dwmIsCompositionEnabled()) + && ((testFlag(OpenGLSurface) && testFlag(OpenGLDoubleBuffered)) || testFlag(VulkanSurface))) + { SelectClipRgn(ps.hdc, NULL); + } // If the a window is obscured by another window (such as a child window) // we still need to send isExposed=true, for compatibility. @@ -1725,20 +1829,16 @@ QWindowsWindowData QWindowsWindow::setWindowFlags_sys(Qt::WindowFlags wt, return result; } -void QWindowsWindow::handleWindowStateChange(Qt::WindowState state) +void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state) { qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << "\n from " << m_windowState << " to " << state; m_windowState = state; QWindowSystemInterface::handleWindowStateChanged(window(), state); - switch (state) { - case Qt::WindowMinimized: + if (state & Qt::WindowMinimized) { handleHidden(); QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now. - break; - case Qt::WindowMaximized: - case Qt::WindowFullScreen: - case Qt::WindowNoState: { + } else { // QTBUG-17548: We send expose events when receiving WM_Paint, but for // layered windows and transient children, we won't receive any WM_Paint. QWindow *w = window(); @@ -1759,13 +1859,9 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowState state) if (exposeEventsSent && !QWindowsContext::instance()->asyncExpose()) QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); } - break; - default: - break; - } } -void QWindowsWindow::setWindowState(Qt::WindowState state) +void QWindowsWindow::setWindowState(Qt::WindowStates state) { if (m_data.hwnd) { setWindowState_sys(state); @@ -1796,18 +1892,19 @@ bool QWindowsWindow::isFullScreen_sys() const to ShowWindow. */ -void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) +void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState) { - const Qt::WindowState oldState = m_windowState; + const Qt::WindowStates oldState = m_windowState; if (oldState == newState) return; qCDebug(lcQpaWindows) << '>' << __FUNCTION__ << this << window() << " from " << oldState << " to " << newState; const bool visible = isVisible(); + auto stateChange = oldState ^ newState; - if ((oldState == Qt::WindowFullScreen) != (newState == Qt::WindowFullScreen)) { - if (newState == Qt::WindowFullScreen) { + if (stateChange & Qt::WindowFullScreen) { + if (newState & Qt::WindowFullScreen) { #ifndef Q_FLATTEN_EXPOSE UINT newStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP; #else @@ -1818,7 +1915,7 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) // Window state but emulated by changing geometry and style. if (!m_savedStyle) { m_savedStyle = style(); - if (oldState == Qt::WindowMinimized || oldState == Qt::WindowMaximized) { + if ((oldState & Qt::WindowMinimized) || (oldState & Qt::WindowMaximized)) { const QRect nf = normalFrameGeometry(m_data.hwnd); if (nf.isValid()) m_savedFrameGeometry = nf; @@ -1826,6 +1923,8 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) m_savedFrameGeometry = frameGeometry_sys(); } } + if (newState & Qt::WindowMaximized) + setFlag(MaximizeToFullScreen); if (m_savedStyle & WS_SYSMENU) newStyle |= WS_SYSMENU; if (visible) @@ -1839,15 +1938,23 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) if (!screen) screen = QGuiApplication::primaryScreen(); const QRect r = screen ? QHighDpi::toNativePixels(screen->geometry(), window()) : m_savedFrameGeometry; - const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE; - const bool wasSync = testFlag(SynchronousGeometryChangeEvent); - setFlag(SynchronousGeometryChangeEvent); - SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); - if (!wasSync) - clearFlag(SynchronousGeometryChangeEvent); - QWindowSystemInterface::handleGeometryChange(window(), r); - QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); - } else if (newState != Qt::WindowMinimized) { + + if (newState & Qt::WindowMinimized) { + setMinimizedGeometry(m_data.hwnd, r); + if (stateChange & Qt::WindowMaximized) + setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized); + } else { + const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE; + const bool wasSync = testFlag(SynchronousGeometryChangeEvent); + setFlag(SynchronousGeometryChangeEvent); + SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); + if (!wasSync) + clearFlag(SynchronousGeometryChangeEvent); + clearFlag(MaximizeToFullScreen); + QWindowSystemInterface::handleGeometryChange(window(), r); + QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); + } + } else { // Restore saved state. unsigned newStyle = m_savedStyle ? m_savedStyle : style(); if (visible) @@ -1861,43 +1968,58 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState) if (!screen->geometry().intersects(m_savedFrameGeometry)) m_savedFrameGeometry.moveTo(screen->geometry().topLeft()); - UINT swpf = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE; - if (!m_savedFrameGeometry.isValid()) - swpf |= SWP_NOSIZE | SWP_NOMOVE; - const bool wasSync = testFlag(SynchronousGeometryChangeEvent); - setFlag(SynchronousGeometryChangeEvent); - // After maximized/fullscreen; the window can be in a maximized state. Clear - // it before applying the normal geometry. - if (windowVisibility_sys(m_data.hwnd) == QWindow::Maximized) - ShowWindow(m_data.hwnd, SW_SHOWNOACTIVATE); - SetWindowPos(m_data.hwnd, 0, m_savedFrameGeometry.x(), m_savedFrameGeometry.y(), - m_savedFrameGeometry.width(), m_savedFrameGeometry.height(), swpf); - if (!wasSync) - clearFlag(SynchronousGeometryChangeEvent); - // preserve maximized state - if (visible) { - setFlag(WithinMaximize); - ShowWindow(m_data.hwnd, (newState == Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNA); - clearFlag(WithinMaximize); + if (newState & Qt::WindowMinimized) { + setMinimizedGeometry(m_data.hwnd, m_savedFrameGeometry); + if (stateChange & Qt::WindowMaximized) + setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized); + } else { + UINT swpf = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE; + if (!m_savedFrameGeometry.isValid()) + swpf |= SWP_NOSIZE | SWP_NOMOVE; + const bool wasSync = testFlag(SynchronousGeometryChangeEvent); + setFlag(SynchronousGeometryChangeEvent); + // After maximized/fullscreen; the window can be in a maximized state. Clear + // it before applying the normal geometry. + if (windowVisibility_sys(m_data.hwnd) == QWindow::Maximized) + ShowWindow(m_data.hwnd, SW_SHOWNOACTIVATE); + SetWindowPos(m_data.hwnd, 0, m_savedFrameGeometry.x(), m_savedFrameGeometry.y(), + m_savedFrameGeometry.width(), m_savedFrameGeometry.height(), swpf); + if (!wasSync) + clearFlag(SynchronousGeometryChangeEvent); + // preserve maximized state + if (visible) { + setFlag(WithinMaximize); + ShowWindow(m_data.hwnd, + (newState & Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNA); + clearFlag(WithinMaximize); + } } m_savedStyle = 0; m_savedFrameGeometry = QRect(); } - } else if ((oldState == Qt::WindowMaximized) != (newState == Qt::WindowMaximized)) { - if (visible && !(newState == Qt::WindowMinimized)) { + } else if ((oldState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized)) { + if (visible && !(newState & Qt::WindowMinimized)) { setFlag(WithinMaximize); - if (newState == Qt::WindowFullScreen) + if (newState & Qt::WindowFullScreen) setFlag(MaximizeToFullScreen); - ShowWindow(m_data.hwnd, (newState == Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNOACTIVATE); + ShowWindow(m_data.hwnd, + (newState & Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNOACTIVATE); clearFlag(WithinMaximize); clearFlag(MaximizeToFullScreen); + } else if (visible && (oldState & newState & Qt::WindowMinimized)) { + // change of the maximized state while keeping minimized + setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized); } } - if ((oldState == Qt::WindowMinimized) != (newState == Qt::WindowMinimized)) { - if (visible) - ShowWindow(m_data.hwnd, (newState == Qt::WindowMinimized) ? SW_MINIMIZE : - (newState == Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNORMAL); + if (stateChange & Qt::WindowMinimized) { + if (visible) { + ShowWindow(m_data.hwnd, + (newState & Qt::WindowMinimized) ? SW_MINIMIZE : + (newState & Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNORMAL); + if ((newState & Qt::WindowMinimized) && (stateChange & Qt::WindowMaximized)) + setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized); + } } qCDebug(lcQpaWindows) << '<' << __FUNCTION__ << this << window() << newState; } @@ -1989,7 +2111,7 @@ void QWindowsWindow::setOpacity(qreal level) m_opacity = level; if (m_data.hwnd) setWindowOpacity(m_data.hwnd, m_data.flags, - window()->format().hasAlpha(), testFlag(OpenGLSurface), + window()->format().hasAlpha(), testFlag(OpenGLSurface) || testFlag(VulkanSurface), level); } } @@ -2158,7 +2280,7 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const hint.applyToMinMaxInfo(m_data.hwnd, mmi); } - if ((testFlag(WithinMaximize) || (window()->windowState() == Qt::WindowMinimized)) + 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 @@ -2188,7 +2310,7 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re // QTBUG-32663, suppress resize cursor for fixed size windows. const QWindow *w = window(); if (!w->isTopLevel() // Task 105852, minimized windows need to respond to user input. - || (m_windowState != Qt::WindowNoState && m_windowState != Qt::WindowActive) + || !(m_windowState & ~Qt::WindowActive) || (m_data.flags & Qt::FramelessWindowHint)) { return false; } @@ -2379,6 +2501,16 @@ bool QWindowsWindow::isTopLevel() const return window()->isTopLevel() && !m_data.embedded; } +QWindowsMenuBar *QWindowsWindow::menuBar() const +{ + return m_menuBar.data(); +} + +void QWindowsWindow::setMenuBar(QWindowsMenuBar *mb) +{ + m_menuBar = mb; +} + /*! \brief Sets custom margins to be added to the default margins determined by the windows style in the handling of the WM_NCCALCSIZE message. @@ -2407,11 +2539,27 @@ void QWindowsWindow::setCustomMargins(const QMargins &newCustomMargins) void *QWindowsWindow::surface(void *nativeConfig, int *err) { -#ifdef QT_NO_OPENGL +#if QT_CONFIG(vulkan) + Q_UNUSED(nativeConfig); + Q_UNUSED(err); + if (window()->surfaceType() == QSurface::VulkanSurface) { + if (!m_vkSurface) { + QVulkanInstance *inst = window()->vulkanInstance(); + if (inst) + m_vkSurface = static_cast<QWindowsVulkanInstance *>(inst->handle())->createSurface(handle()); + else + qWarning("Attempted to create Vulkan surface without an instance; was QWindow::setVulkanInstance() called?"); + } + // Different semantics for VkSurfaces: the return value is the address, + // not the value, given that this is a 64-bit handle even on x86. + return &m_vkSurface; + } +#elif defined(QT_NO_OPENGL) Q_UNUSED(err) Q_UNUSED(nativeConfig) return 0; -#else +#endif +#ifndef QT_NO_OPENGL if (!m_surface) { if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, err); @@ -2423,6 +2571,14 @@ void *QWindowsWindow::surface(void *nativeConfig, int *err) void QWindowsWindow::invalidateSurface() { +#if QT_CONFIG(vulkan) + if (m_vkSurface) { + QVulkanInstance *inst = window()->vulkanInstance(); + if (inst) + static_cast<QWindowsVulkanInstance *>(inst->handle())->destroySurface(m_vkSurface); + m_vkSurface = 0; + } +#endif #ifndef QT_NO_OPENGL if (m_surface) { if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 2b447751ba..414d4a92f8 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -41,14 +41,20 @@ #define QWINDOWSWINDOW_H #include <QtCore/qt_windows.h> +#include <QtCore/QPointer> #include "qwindowscursor.h" #include <qpa/qplatformwindow.h> #include <QtPlatformHeaders/qwindowswindowfunctions.h> +#if QT_CONFIG(vulkan) +#include "qwindowsvulkaninstance.h" +#endif + QT_BEGIN_NAMESPACE class QWindowsOleDropTarget; +class QWindowsMenuBar; class QDebug; struct QWindowsGeometryHint @@ -75,9 +81,10 @@ struct QWindowsGeometryHint struct QWindowCreationContext { - QWindowCreationContext(const QWindow *w, const QRect &r, - const QMargins &customMargins, - DWORD style, DWORD exStyle); + explicit QWindowCreationContext(const QWindow *w, + const QRect &geometryIn, const QRect &geometry, + const QMargins &customMargins, + DWORD style, DWORD exStyle); void applyToMinMaxInfo(MINMAXINFO *mmi) const { geometryHint.applyToMinMaxInfo(style, exStyle, mmi); } @@ -85,7 +92,8 @@ struct QWindowCreationContext const QWindow *window; DWORD style; DWORD exStyle; - QRect requestedGeometry; + QRect requestedGeometryIn; // QWindow scaled + QRect requestedGeometry; // after QPlatformWindow::initialGeometry() QRect obtainedGeometry; QMargins margins; QMargins customMargins; // User-defined, additional frame for WM_NCCALCSIZE @@ -188,6 +196,7 @@ public: { AutoMouseCapture = 0x1, //! Automatic mouse capture on button press. WithinSetParent = 0x2, + WithinSetGeometry = 0x8, OpenGLSurface = 0x10, OpenGL_ES2 = 0x20, OpenGLDoubleBuffered = 0x40, @@ -208,12 +217,15 @@ public: Compositing = 0x200000, HasBorderInFullScreen = 0x400000, WithinDpiChanged = 0x800000, + VulkanSurface = 0x1000000, ResizeMoveActive = 0x2000000 }; QWindowsWindow(QWindow *window, const QWindowsWindowData &data); ~QWindowsWindow(); + void initialize() override; + using QPlatformWindow::screenForGeometry; QSurfaceFormat format() const override { return m_format; } @@ -231,7 +243,7 @@ public: QPoint mapFromGlobal(const QPoint &pos) const override; void setWindowFlags(Qt::WindowFlags flags) override; - void setWindowState(Qt::WindowState state) override; + void setWindowState(Qt::WindowStates state) override; void setParent(const QPlatformWindow *window) override; @@ -265,6 +277,9 @@ public: HWND handle() const override { return m_data.hwnd; } bool isTopLevel() const override; + QWindowsMenuBar *menuBar() const; + void setMenuBar(QWindowsMenuBar *mb); + QMargins customMargins() const { return m_data.customMargins; } void setCustomMargins(const QMargins &m); @@ -328,7 +343,7 @@ private: inline void show_sys() const; inline QWindowsWindowData setWindowFlags_sys(Qt::WindowFlags wt, unsigned flags = 0) const; inline bool isFullScreen_sys() const; - inline void setWindowState_sys(Qt::WindowState newState); + inline void setWindowState_sys(Qt::WindowStates newState); inline void setParent_sys(const QPlatformWindow *parent); inline void updateTransientParent() const; void destroyWindow(); @@ -336,14 +351,15 @@ private: void setDropSiteEnabled(bool enabled); void updateDropSite(bool topLevel); void handleGeometryChange(); - void handleWindowStateChange(Qt::WindowState state); + void handleWindowStateChange(Qt::WindowStates state); inline void destroyIcon(); void fireExpose(const QRegion ®ion, bool force=false); mutable QWindowsWindowData m_data; + QPointer<QWindowsMenuBar> m_menuBar; mutable unsigned m_flags = WithinCreate; HDC m_hdc = 0; - Qt::WindowState m_windowState = Qt::WindowNoState; + Qt::WindowStates m_windowState = Qt::WindowNoState; qreal m_opacity = 1; #ifndef QT_NO_CURSOR CursorHandlePtr m_cursor; @@ -355,6 +371,11 @@ private: HICON m_iconSmall = 0; HICON m_iconBig = 0; void *m_surface = nullptr; + +#if QT_CONFIG(vulkan) + // note: intentionally not using void * in order to avoid breaking x86 + VkSurfaceKHR m_vkSurface = 0; +#endif }; #ifndef QT_NO_DEBUG_STREAM @@ -364,6 +385,7 @@ QDebug operator<<(QDebug d, const MINMAXINFO &i); QDebug operator<<(QDebug d, const NCCALCSIZE_PARAMS &p); QDebug operator<<(QDebug d, const WINDOWPLACEMENT &); QDebug operator<<(QDebug d, const WINDOWPOS &); +QDebug operator<<(QDebug d, const GUID &guid); #endif // !QT_NO_DEBUG_STREAM // ---------- QWindowsGeometryHint inline functions. diff --git a/src/plugins/platforms/windows/windows.pri b/src/plugins/platforms/windows/windows.pri index 6d01d05fcc..b7790a66e3 100644 --- a/src/plugins/platforms/windows/windows.pri +++ b/src/plugins/platforms/windows/windows.pri @@ -19,11 +19,13 @@ SOURCES += \ $$PWD/qwindowskeymapper.cpp \ $$PWD/qwindowsmousehandler.cpp \ $$PWD/qwindowsole.cpp \ + $$PWD/qwindowsdropdataobject.cpp \ $$PWD/qwindowsmime.cpp \ $$PWD/qwindowsinternalmimedata.cpp \ $$PWD/qwindowscursor.cpp \ $$PWD/qwindowsinputcontext.cpp \ $$PWD/qwindowstheme.cpp \ + $$PWD/qwindowsmenu.cpp \ $$PWD/qwindowsdialoghelpers.cpp \ $$PWD/qwindowsservices.cpp \ $$PWD/qwindowsnativeinterface.cpp \ @@ -31,6 +33,7 @@ SOURCES += \ $$PWD/qwin10helpers.cpp HEADERS += \ + $$PWD/qwindowscombase.h \ $$PWD/qwindowswindow.h \ $$PWD/qwindowsintegration.h \ $$PWD/qwindowscontext.h \ @@ -39,11 +42,13 @@ HEADERS += \ $$PWD/qwindowsmousehandler.h \ $$PWD/qtwindowsglobal.h \ $$PWD/qwindowsole.h \ + $$PWD/qwindowsdropdataobject.h \ $$PWD/qwindowsmime.h \ $$PWD/qwindowsinternalmimedata.h \ $$PWD/qwindowscursor.h \ $$PWD/qwindowsinputcontext.h \ $$PWD/qwindowstheme.h \ + $$PWD/qwindowsmenu.h \ $$PWD/qwindowsdialoghelpers.h \ $$PWD/qwindowsservices.h \ $$PWD/qwindowsnativeinterface.h \ @@ -69,6 +74,16 @@ qtConfig(dynamicgl) { HEADERS += $$PWD/qwindowseglcontext.h } +qtConfig(systemtrayicon) { + SOURCES += $$PWD/qwindowssystemtrayicon.cpp + HEADERS += $$PWD/qwindowssystemtrayicon.h +} + +qtConfig(vulkan) { + SOURCES += $$PWD/qwindowsvulkaninstance.cpp + HEADERS += $$PWD/qwindowsvulkaninstance.h +} + qtConfig(clipboard) { SOURCES += $$PWD/qwindowsclipboard.cpp HEADERS += $$PWD/qwindowsclipboard.h diff --git a/src/plugins/platforms/windows/windows.pro b/src/plugins/platforms/windows/windows.pro index c5d76c5d1d..174bc7b609 100644 --- a/src/plugins/platforms/windows/windows.pro +++ b/src/plugins/platforms/windows/windows.pro @@ -6,6 +6,7 @@ QT += \ fontdatabase_support-private theme_support-private qtConfig(accessibility): QT += accessibility_support-private +qtConfig(vulkan): QT += vulkan_support-private LIBS += -lgdi32 -ldwmapi diff --git a/src/plugins/platforms/winrt/qwinrtdrag.cpp b/src/plugins/platforms/winrt/qwinrtdrag.cpp index 15ae024d20..43c406e1fb 100644 --- a/src/plugins/platforms/winrt/qwinrtdrag.cpp +++ b/src/plugins/platforms/winrt/qwinrtdrag.cpp @@ -773,12 +773,6 @@ void QWinRTDrag::setDropTarget(QWindow *target) m_dragTarget = target; } -QMimeData *QWinRTDrag::platformDropData() -{ - qCDebug(lcQpaMime) << __FUNCTION__; - return m_mimeData; -} - void QWinRTDrag::setUiElement(ComPtr<ABI::Windows::UI::Xaml::IUIElement> &element) { qCDebug(lcQpaMime) << __FUNCTION__; diff --git a/src/plugins/platforms/winrt/qwinrtdrag.h b/src/plugins/platforms/winrt/qwinrtdrag.h index 6cbabfbf41..2371201507 100644 --- a/src/plugins/platforms/winrt/qwinrtdrag.h +++ b/src/plugins/platforms/winrt/qwinrtdrag.h @@ -94,7 +94,6 @@ public: virtual ~QWinRTDrag(); static QWinRTDrag *instance(); - QMimeData *platformDropData(void) override; Qt::DropAction drag(QDrag *) override; void setDropTarget(QWindow *target); diff --git a/src/plugins/platforms/winrt/qwinrtfileengine.cpp b/src/plugins/platforms/winrt/qwinrtfileengine.cpp index 58375d331c..f037c516b5 100644 --- a/src/plugins/platforms/winrt/qwinrtfileengine.cpp +++ b/src/plugins/platforms/winrt/qwinrtfileengine.cpp @@ -414,10 +414,11 @@ QDateTime QWinRTFileEngine::fileTime(FileTime type) const HRESULT hr; DateTime dateTime = { 0 }; switch (type) { - case CreationTime: + case BirthTime: hr = d->file->get_DateCreated(&dateTime); RETURN_IF_FAILED("Failed to get file creation time", return QDateTime()); break; + case MetadataChangeTime: case ModificationTime: case AccessTime: { ComPtr<IAsyncOperation<FileProperties::BasicProperties *>> op; diff --git a/src/plugins/platforms/winrt/qwinrtwindow.cpp b/src/plugins/platforms/winrt/qwinrtwindow.cpp index c40a1b8c45..cbf0ba36c9 100644 --- a/src/plugins/platforms/winrt/qwinrtwindow.cpp +++ b/src/plugins/platforms/winrt/qwinrtwindow.cpp @@ -91,7 +91,7 @@ public: QSurfaceFormat surfaceFormat; QString windowTitle; - Qt::WindowState state; + Qt::WindowStates state; EGLDisplay display; EGLSurface surface; @@ -158,7 +158,7 @@ QWinRTWindow::QWinRTWindow(QWindow *window) Q_ASSERT_SUCCEEDED(hr); setWindowFlags(window->flags()); - setWindowState(window->windowState()); + setWindowState(window->windowStates()); setWindowTitle(window->title()); setGeometry(window->geometry()); @@ -323,7 +323,7 @@ qreal QWinRTWindow::devicePixelRatio() const return screen()->devicePixelRatio(); } -void QWinRTWindow::setWindowState(Qt::WindowState state) +void QWinRTWindow::setWindowState(Qt::WindowStates state) { Q_D(QWinRTWindow); qCDebug(lcQpaWindows) << __FUNCTION__ << this << state; @@ -331,7 +331,13 @@ void QWinRTWindow::setWindowState(Qt::WindowState state) if (d->state == state) return; - if (state == Qt::WindowFullScreen) { + if (state & Qt::WindowMinimized) { + setUIElementVisibility(d->uiElement.Get(), false); + d->state = state; + return; + } + + if (state & Qt::WindowFullScreen) { HRESULT hr; boolean success; hr = QEventDispatcherWinRT::runOnXamlThread([&hr, &success]() { @@ -356,7 +362,7 @@ void QWinRTWindow::setWindowState(Qt::WindowState state) return; } - if (d->state == Qt::WindowFullScreen) { + if (d->state & Qt::WindowFullScreen) { HRESULT hr; hr = QEventDispatcherWinRT::runOnXamlThread([&hr]() { ComPtr<IApplicationViewStatics2> applicationViewStatics; @@ -378,10 +384,7 @@ void QWinRTWindow::setWindowState(Qt::WindowState state) } } - if (state == Qt::WindowMinimized) - setUIElementVisibility(d->uiElement.Get(), false); - - if (d->state == Qt::WindowMinimized || state == Qt::WindowNoState || state == Qt::WindowActive) + if (d->state & Qt::WindowMinimized || state == Qt::WindowNoState || state == Qt::WindowActive) setUIElementVisibility(d->uiElement.Get(), true); d->state = state; diff --git a/src/plugins/platforms/winrt/qwinrtwindow.h b/src/plugins/platforms/winrt/qwinrtwindow.h index 26c2fa800d..a8992450b9 100644 --- a/src/plugins/platforms/winrt/qwinrtwindow.h +++ b/src/plugins/platforms/winrt/qwinrtwindow.h @@ -68,7 +68,7 @@ public: WId winId() const override; qreal devicePixelRatio() const override; - void setWindowState(Qt::WindowState state) override; + void setWindowState(Qt::WindowStates state) override; bool setMouseGrabEnabled(bool grab) Q_DECL_OVERRIDE; bool setKeyboardGrabEnabled(bool grab) Q_DECL_OVERRIDE; diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglcontext.h b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglcontext.h index 48e774bbb2..fe50afa62a 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglcontext.h +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglcontext.h @@ -47,38 +47,28 @@ QT_BEGIN_NAMESPACE -//####todo remove the noops (looks like their where there in the initial commit) class QXcbEglContext : public QEGLPlatformContext { public: QXcbEglContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share, - EGLDisplay display, QXcbConnection *c, const QVariant &nativeHandle) + EGLDisplay display, const QVariant &nativeHandle) : QEGLPlatformContext(glFormat, share, display, 0, nativeHandle) - , m_connection(c) { - Q_XCB_NOOP(m_connection); } void swapBuffers(QPlatformSurface *surface) { - Q_XCB_NOOP(m_connection); QEGLPlatformContext::swapBuffers(surface); - Q_XCB_NOOP(m_connection); } bool makeCurrent(QPlatformSurface *surface) { - Q_XCB_NOOP(m_connection); - bool ret = QEGLPlatformContext::makeCurrent(surface); - Q_XCB_NOOP(m_connection); - return ret; + return QEGLPlatformContext::makeCurrent(surface); } void doneCurrent() { - Q_XCB_NOOP(m_connection); QEGLPlatformContext::doneCurrent(); - Q_XCB_NOOP(m_connection); } EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface) @@ -92,9 +82,6 @@ public: QVariant nativeHandle() const { return QVariant::fromValue<QEGLNativeContext>(QEGLNativeContext(eglContext(), eglDisplay())); } - -private: - QXcbConnection *m_connection; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp index 9c52733120..7aa1d631df 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp @@ -102,7 +102,6 @@ QPlatformOpenGLContext *QXcbEglIntegration::createPlatformOpenGLContext(QOpenGLC QXcbEglContext *platformContext = new QXcbEglContext(screen->surfaceFormatFor(context->format()), context->shareHandle(), eglDisplay(), - screen->connection(), context->nativeHandle()); context->setNativeHandle(platformContext->nativeHandle()); return platformContext; diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp index e2e573f0e1..3bc8590d36 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp @@ -167,7 +167,7 @@ static void updateFormatFromContext(QSurfaceFormat &format) QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlatformOpenGLContext *share, const QVariant &nativeHandle) : QPlatformOpenGLContext() - , m_display(DISPLAY_FROM_XCB(screen)) + , m_display(static_cast<Display *>(screen->connection()->xlib_display())) , m_config(0) , m_context(0) , m_shareContext(0) @@ -196,7 +196,7 @@ void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share) if (share) m_shareContext = static_cast<const QGLXContext*>(share)->glxContext(); - GLXFBConfig config = qglx_findConfig(DISPLAY_FROM_XCB(screen),screen->screenNumber(),m_format); + GLXFBConfig config = qglx_findConfig(m_display, screen->screenNumber(), m_format); m_config = config; XVisualInfo *visualInfo = 0; Window window = 0; // Temporary window used to query OpenGL context @@ -304,10 +304,10 @@ void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share) // Get the basic surface format details if (m_context) - qglx_surfaceFormatFromGLXFBConfig(&m_format, DISPLAY_FROM_XCB(screen), config); + qglx_surfaceFormatFromGLXFBConfig(&m_format, m_display, config); // Create a temporary window so that we can make the new context current - window = createDummyWindow(DISPLAY_FROM_XCB(screen), config, screen->screenNumber(), screen->root()); + window = createDummyWindow(m_display, config, screen->screenNumber(), screen->root()); } else { // requesting an OpenGL ES context requires glXCreateContextAttribsARB, so bail out if (m_format.renderableType() == QSurfaceFormat::OpenGLES) @@ -325,7 +325,7 @@ void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share) } // Create a temporary window so that we can make the new context current - window = createDummyWindow(DISPLAY_FROM_XCB(screen), visualInfo, screen->screenNumber(), screen->root()); + window = createDummyWindow(m_display, visualInfo, screen->screenNumber(), screen->root()); XFree(visualInfo); } @@ -360,7 +360,7 @@ void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share, const // Use the provided Display, if available. If not, use our own. It may still work. Display *dpy = handle.display(); if (!dpy) - dpy = DISPLAY_FROM_XCB(screen); + dpy = m_display; // Legacy contexts created using glXCreateContext are created using a visual // and the FBConfig cannot be queried. The only way to adapt these contexts @@ -665,8 +665,10 @@ void QGLXContext::queryDummyContext() Display *display = glXGetCurrentDisplay(); if (!display) { // FIXME: Since Qt 5.6 we don't need to check whether primary screen is NULL - if (QScreen *screen = QGuiApplication::primaryScreen()) - display = DISPLAY_FROM_XCB(static_cast<QXcbScreen *>(screen->handle())); + if (QScreen *screen = QGuiApplication::primaryScreen()) { + QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(screen->handle()); + display = static_cast<Display *>(xcbScreen->connection()->xlib_display()); + } } const char *glxvendor = glXGetClientString(display, GLX_VENDOR); if (glxvendor && !strcmp(glxvendor, "ATI")) { @@ -729,8 +731,7 @@ void QGLXContext::queryDummyContext() bool QGLXContext::supportsThreading() { - if (!m_queriedDummyContext) - queryDummyContext(); + queryDummyContext(); return m_supportsThreading; } @@ -738,9 +739,10 @@ QGLXPbuffer::QGLXPbuffer(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) , m_screen(static_cast<QXcbScreen *>(offscreenSurface->screen()->handle())) , m_format(m_screen->surfaceFormatFor(offscreenSurface->requestedFormat())) + , m_display(static_cast<Display *>(m_screen->connection()->xlib_display())) , m_pbuffer(0) { - GLXFBConfig config = qglx_findConfig(DISPLAY_FROM_XCB(m_screen), m_screen->screenNumber(), m_format); + GLXFBConfig config = qglx_findConfig(m_display, m_screen->screenNumber(), m_format); if (config) { const int attributes[] = { @@ -751,17 +753,17 @@ QGLXPbuffer::QGLXPbuffer(QOffscreenSurface *offscreenSurface) None }; - m_pbuffer = glXCreatePbuffer(DISPLAY_FROM_XCB(m_screen), config, attributes); + m_pbuffer = glXCreatePbuffer(m_display, config, attributes); if (m_pbuffer) - qglx_surfaceFormatFromGLXFBConfig(&m_format, DISPLAY_FROM_XCB(m_screen), config); + qglx_surfaceFormatFromGLXFBConfig(&m_format, m_display, config); } } QGLXPbuffer::~QGLXPbuffer() { if (m_pbuffer) - glXDestroyPbuffer(DISPLAY_FROM_XCB(m_screen), m_pbuffer); + glXDestroyPbuffer(m_display, m_pbuffer); } diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h index 3dfe0ac618..f6372582db 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h @@ -108,6 +108,7 @@ public: private: QXcbScreen *m_screen; QSurfaceFormat m_format; + Display *m_display; GLXPbuffer m_pbuffer; }; diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp index fea365cabc..377066df61 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp @@ -108,18 +108,13 @@ bool QXcbGlxIntegration::initialize(QXcbConnection *connection) m_glx_first_event = reply->first_event; - xcb_generic_error_t *error = 0; - xcb_glx_query_version_cookie_t xglx_query_cookie = xcb_glx_query_version(m_connection->xcb_connection(), - XCB_GLX_MAJOR_VERSION, - XCB_GLX_MINOR_VERSION); - xcb_glx_query_version_reply_t *xglx_query = xcb_glx_query_version_reply(m_connection->xcb_connection(), - xglx_query_cookie, &error); - if (!xglx_query || error) { + auto xglx_query = Q_XCB_REPLY(xcb_glx_query_version, m_connection->xcb_connection(), + XCB_GLX_MAJOR_VERSION, + XCB_GLX_MINOR_VERSION); + if (!xglx_query) { qCWarning(lcQpaGl) << "QXcbConnection: Failed to initialize GLX"; - free(error); return false; } - free(xglx_query); #endif m_native_interface_handler.reset(new QXcbGlxNativeInterfaceHandler(connection->nativeInterface())); diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp index 8df8b28f72..145a11a5e3 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp @@ -41,6 +41,7 @@ #include "qxcbscreen.h" #include <QtGlxSupport/private/qglxconvenience_p.h> +#include <QDebug> QT_BEGIN_NAMESPACE @@ -58,13 +59,28 @@ const xcb_visualtype_t *QXcbGlxWindow::createVisual() QXcbScreen *scr = xcbScreen(); if (!scr) return Q_NULLPTR; - XVisualInfo *visualInfo = qglx_findVisualInfo(DISPLAY_FROM_XCB(scr), scr->screenNumber(), &m_format); + + qDebug(lcQpaGl) << "Requested format before FBConfig/Visual selection:" << m_format; + + Display *dpy = static_cast<Display *>(scr->connection()->xlib_display()); + const char *glxExts = glXQueryExtensionsString(dpy, scr->screenNumber()); + int flags = 0; + if (glxExts) { + qCDebug(lcQpaGl, "Available GLX extensions: %s", glxExts); + if (strstr(glxExts, "GLX_EXT_framebuffer_sRGB") || strstr(glxExts, "GLX_ARB_framebuffer_sRGB")) + flags |= QGLX_SUPPORTS_SRGB; + } + + XVisualInfo *visualInfo = qglx_findVisualInfo(dpy, scr->screenNumber(), &m_format, GLX_WINDOW_BIT, flags); if (!visualInfo) { qWarning() << "No XVisualInfo for format" << m_format; return Q_NULLPTR; } const xcb_visualtype_t *xcb_visualtype = scr->visualForId(visualInfo->visualid); XFree(visualInfo); + + qDebug(lcQpaGl) << "Got format:" << m_format; + return xcb_visualtype; } diff --git a/src/plugins/platforms/xcb/nativepainting/nativepainting.pri b/src/plugins/platforms/xcb/nativepainting/nativepainting.pri new file mode 100644 index 0000000000..78ed00843f --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/nativepainting.pri @@ -0,0 +1,22 @@ +qtConfig(xcb-native-painting) { + qtConfig(xrender): QMAKE_USE += xrender + qtConfig(fontconfig): QMAKE_USE_PRIVATE += freetype + + INCLUDEPATH += $$PWD + HEADERS += \ + $$PWD/qtessellator_p.h \ + $$PWD/qpixmap_x11_p.h \ + $$PWD/qpaintengine_x11_p.h \ + $$PWD/qt_x11_p.h \ + $$PWD/qcolormap_x11_p.h \ + $$PWD/qbackingstore_x11_p.h \ + $$PWD/qxcbnativepainting.h + + SOURCES += \ + $$PWD/qtessellator.cpp \ + $$PWD/qpixmap_x11.cpp \ + $$PWD/qpaintengine_x11.cpp \ + $$PWD/qcolormap_x11.cpp \ + $$PWD/qbackingstore_x11.cpp \ + $$PWD/qxcbnativepainting.cpp +} diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp new file mode 100644 index 0000000000..2dd2cdd9e3 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbackingstore_x11_p.h" +#include "qxcbwindow.h" +#include "qpixmap_x11_p.h" + +#include <private/qhighdpiscaling_p.h> +#include <QPainter> + +#if QT_CONFIG(xrender) +# include <X11/extensions/Xrender.h> +#endif + +#include <X11/Xlib.h> + +#ifndef None +#define None 0L +#endif + +QT_BEGIN_NAMESPACE + +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; +} + +QXcbNativeBackingStore::~QXcbNativeBackingStore() +{} + +QPaintDevice *QXcbNativeBackingStore::paintDevice() +{ + return &m_pixmap; +} + +void QXcbNativeBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +{ + if (m_pixmap.isNull()) + return; + + QSize pixmapSize = m_pixmap.size(); + + QRegion clipped = region; + clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window)); + clipped &= QRect(0, 0, pixmapSize.width(), pixmapSize.height()).translated(-offset); + + QRect br = clipped.boundingRect(); + if (br.isNull()) + return; + + QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); + if (!platformWindow) { + qWarning("QXcbBackingStore::flush: QWindow has no platform window (QTBUG-32681)"); + return; + } + + Window wid = platformWindow->xcb_window(); + Pixmap pid = qt_x11PixmapHandle(m_pixmap); + + QVector<XRectangle> clipRects = qt_region_to_xrectangles(clipped); + +#if QT_CONFIG(xrender) + if (m_translucentBackground) + { + XWindowAttributes attrib; + XGetWindowAttributes(display(), wid, &attrib); + XRenderPictFormat *format = XRenderFindVisualFormat(display(), attrib.visual); + + Picture srcPic = qt_x11PictureHandle(m_pixmap); + Picture dstPic = XRenderCreatePicture(display(), wid, format, 0, 0); + + XRenderSetPictureClipRectangles(display(), dstPic, 0, 0, clipRects.constData(), clipRects.size()); + + XRenderComposite(display(), PictOpSrc, srcPic, None, dstPic, + br.x() + offset.x(), br.y() + offset.y(), + 0, 0, + br.x(), br.y(), + br.width(), br.height()); + + XRenderFreePicture(display(), dstPic); + } + else +#endif + { + GC gc = XCreateGC(display(), wid, 0, Q_NULLPTR); + + if (clipRects.size() != 1) + XSetClipRectangles(display(), gc, 0, 0, clipRects.data(), clipRects.size(), YXBanded); + + XCopyArea(display(), pid, wid, gc, br.x() + offset.x(), br.y() + offset.y(), br.width(), br.height(), br.x(), br.y()); + XFreeGC(display(), gc); + } + + + if (platformWindow->needsSync()) { + platformWindow->updateSyncRequestCounter(); + } else { + XFlush(display()); + } +} + +QImage QXcbNativeBackingStore::toImage() const +{ + return m_pixmap.toImage(); +} + +void QXcbNativeBackingStore::resize(const QSize &size, const QRegion &staticContents) +{ + if (size == m_pixmap.size()) + return; + + QPixmap newPixmap(size); + +#if QT_CONFIG(xrender) + if (m_translucentBackground && newPixmap.depth() != 32) + qt_x11Pixmap(newPixmap)->convertToARGB32(); +#endif + + if (!m_pixmap.isNull()) { + Pixmap from = qt_x11PixmapHandle(m_pixmap); + Pixmap to = qt_x11PixmapHandle(newPixmap); + QRect br = staticContents.boundingRect().intersected(QRect(QPoint(0, 0), size)); + + if (!br.isEmpty()) { + GC gc = XCreateGC(display(), to, 0, Q_NULLPTR); + XCopyArea(display(), from, to, gc, br.x(), br.y(), br.width(), br.height(), br.x(), br.y()); + XFreeGC(display(), gc); + } + } + + m_pixmap = newPixmap; +} + +bool QXcbNativeBackingStore::scroll(const QRegion &area, int dx, int dy) +{ + if (m_pixmap.isNull()) + return false; + + QRect rect = area.boundingRect(); + Pixmap pix = qt_x11PixmapHandle(m_pixmap); + + GC gc = XCreateGC(display(), pix, 0, Q_NULLPTR); + XCopyArea(display(), pix, pix, gc, + rect.x(), rect.y(), rect.width(), rect.height(), + rect.x()+dx, rect.y()+dy); + XFreeGC(display(), gc); + return true; +} + +void QXcbNativeBackingStore::beginPaint(const QRegion ®ion) +{ +#if QT_CONFIG(xrender) + if (m_translucentBackground) { + const QVector<XRectangle> xrects = qt_region_to_xrectangles(region); + const XRenderColor color = { 0, 0, 0, 0 }; + XRenderFillRectangles(display(), PictOpSrc, + qt_x11PictureHandle(m_pixmap), &color, + xrects.constData(), xrects.size()); + } +#else + Q_UNUSED(region); +#endif +} + +Display *QXcbNativeBackingStore::display() const +{ + return static_cast<Display *>(static_cast<QXcbWindow *>(window()->handle())->connection()->xlib_display()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h new file mode 100644 index 0000000000..5f4c24ec11 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBACKINGSTORE_X11_H +#define QBACKINGSTORE_X11_H + +#include <qpa/qplatformbackingstore.h> + +typedef struct _XDisplay Display; + +QT_BEGIN_NAMESPACE + +class QXcbWindow; + +class QXcbNativeBackingStore : public QPlatformBackingStore +{ +public: + QXcbNativeBackingStore(QWindow *window); + ~QXcbNativeBackingStore(); + + QPaintDevice *paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + + QImage toImage() const override; + + void resize(const QSize &size, const QRegion &staticContents) override; + bool scroll(const QRegion &area, int dx, int dy) override; + + void beginPaint(const QRegion ®ion) override; + +private: + Display *display() const; + + QPixmap m_pixmap; + bool m_translucentBackground; +}; + +QT_END_NAMESPACE + +#endif // QBACKINGSTORE_X11_H diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp new file mode 100644 index 0000000000..8554c5445d --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp @@ -0,0 +1,644 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QVarLengthArray> + +#include <private/qguiapplication_p.h> + +#include "qcolormap_x11_p.h" +#include "qxcbnativepainting.h" +#include "qt_x11_p.h" + +QT_BEGIN_NAMESPACE + +class QXcbColormapPrivate +{ +public: + QXcbColormapPrivate() + : ref(1), mode(QXcbColormap::Direct), depth(0), + colormap(0), defaultColormap(true), + visual(0), defaultVisual(true), + r_max(0), g_max(0), b_max(0), + r_shift(0), g_shift(0), b_shift(0) + {} + + QAtomicInt ref; + + QXcbColormap::Mode mode; + int depth; + + Colormap colormap; + bool defaultColormap; + + Visual *visual; + bool defaultVisual; + + int r_max; + int g_max; + int b_max; + + uint r_shift; + uint g_shift; + uint b_shift; + + QVector<QColor> colors; + QVector<int> pixels; +}; + +static uint right_align(uint v) +{ + while (!(v & 0x1)) + v >>= 1; + return v; +} + +static int cube_root(int v) +{ + if (v == 1) + return 1; + // brute force algorithm + int i = 1; + for (;;) { + const int b = i * i * i; + if (b <= v) { + ++i; + } else { + --i; + break; + } + } + return i; +} + +static Visual *find_visual(Display *display, + int screen, + int visual_class, + int visual_id, + int *depth, + bool *defaultVisual) +{ + XVisualInfo *vi, rvi; + int count; + + uint mask = VisualScreenMask; + rvi.screen = screen; + + if (visual_class != -1) { + rvi.c_class = visual_class; + mask |= VisualClassMask; + } + if (visual_id != -1) { + rvi.visualid = visual_id; + mask |= VisualIDMask; + } + + Visual *visual = DefaultVisual(display, screen); + *defaultVisual = true; + *depth = DefaultDepth(display, screen); + + vi = XGetVisualInfo(display, mask, &rvi, &count); + if (vi) { + int best = 0; + for (int x = 0; x < count; ++x) { + if (vi[x].depth > vi[best].depth) + best = x; + } + if (best >= 0 && best <= count && vi[best].visualid != XVisualIDFromVisual(visual)) { + visual = vi[best].visual; + *defaultVisual = (visual == DefaultVisual(display, screen)); + *depth = vi[best].depth; + } + } + if (vi) + XFree((char *)vi); + return visual; +} + +static void query_colormap(QXcbColormapPrivate *d, int screen) +{ + Display *display = X11->display; + + // query existing colormap + int q_colors = (((1u << d->depth) > 256u) ? 256u : (1u << d->depth)); + XColor queried[256]; + memset(queried, 0, sizeof(queried)); + for (int x = 0; x < q_colors; ++x) + queried[x].pixel = x; + XQueryColors(display, d->colormap, queried, q_colors); + + d->colors.resize(q_colors); + for (int x = 0; x < q_colors; ++x) { + if (queried[x].red == 0 + && queried[x].green == 0 + && queried[x].blue == 0 + && queried[x].pixel != BlackPixel(display, screen)) { + // unallocated color cell, skip it + continue; + } + + d->colors[x] = QColor::fromRgbF(queried[x].red / float(USHRT_MAX), + queried[x].green / float(USHRT_MAX), + queried[x].blue / float(USHRT_MAX)); + } + + // for missing colors, find the closest color in the existing colormap + Q_ASSERT(d->pixels.size()); + for (int x = 0; x < d->pixels.size(); ++x) { + if (d->pixels.at(x) != -1) + continue; + + QRgb rgb; + if (d->mode == QXcbColormap::Indexed) { + const int r = (x / (d->g_max * d->b_max)) % d->r_max; + const int g = (x / d->b_max) % d->g_max; + const int b = x % d->b_max; + rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), + (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), + (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); + } else { + rgb = qRgb(x, x, x); + } + + // find closest color + int mindist = INT_MAX, best = -1; + for (int y = 0; y < q_colors; ++y) { + int r = qRed(rgb) - (queried[y].red >> 8); + int g = qGreen(rgb) - (queried[y].green >> 8); + int b = qBlue(rgb) - (queried[y].blue >> 8); + int dist = (r * r) + (g * g) + (b * b); + if (dist < mindist) { + mindist = dist; + best = y; + } + } + + Q_ASSERT(best >= 0 && best < q_colors); + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = queried[best].red; + xcolor.green = queried[best].green; + xcolor.blue = queried[best].blue; + xcolor.pixel = queried[best].pixel; + + if (XAllocColor(display, d->colormap, &xcolor)) { + d->pixels[x] = xcolor.pixel; + } else { + // some weird stuff is going on... + d->pixels[x] = (qGray(rgb) < 127 + ? BlackPixel(display, screen) + : WhitePixel(display, screen)); + } + } else { + d->pixels[x] = best; + } + } +} + +static void init_gray(QXcbColormapPrivate *d, int screen) +{ + d->pixels.resize(d->r_max); + + for (int g = 0; g < d->g_max; ++g) { + const int gray = (g * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1); + const QRgb rgb = qRgb(gray, gray, gray); + + d->pixels[g] = -1; + + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = qRed(rgb) * 0x101; + xcolor.green = qGreen(rgb) * 0x101; + xcolor.blue = qBlue(rgb) * 0x101; + xcolor.pixel = 0ul; + + if (XAllocColor(X11->display, d->colormap, &xcolor)) + d->pixels[g] = xcolor.pixel; + } + } + + query_colormap(d, screen); +} + +static void init_indexed(QXcbColormapPrivate *d, int screen) +{ + d->pixels.resize(d->r_max * d->g_max * d->b_max); + + // create color cube + for (int x = 0, r = 0; r < d->r_max; ++r) { + for (int g = 0; g < d->g_max; ++g) { + for (int b = 0; b < d->b_max; ++b, ++x) { + const QRgb rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), + (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), + (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); + + d->pixels[x] = -1; + + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = qRed(rgb) * 0x101; + xcolor.green = qGreen(rgb) * 0x101; + xcolor.blue = qBlue(rgb) * 0x101; + xcolor.pixel = 0ul; + + if (XAllocColor(X11->display, d->colormap, &xcolor)) + d->pixels[x] = xcolor.pixel; + } + } + } + } + + query_colormap(d, screen); +} + +static void init_direct(QXcbColormapPrivate *d, bool ownColormap) +{ + if (d->visual->c_class != DirectColor || !ownColormap) + return; + + // preallocate 768 on the stack, so that we don't have to malloc + // for the common case (<= 24 bpp) + QVarLengthArray<XColor, 768> colorTable(d->r_max + d->g_max + d->b_max); + int i = 0; + + for (int r = 0; r < d->r_max; ++r) { + colorTable[i].red = r << 8 | r; + colorTable[i].pixel = r << d->r_shift; + colorTable[i].flags = DoRed; + ++i; + } + + for (int g = 0; g < d->g_max; ++g) { + colorTable[i].green = g << 8 | g; + colorTable[i].pixel = g << d->g_shift; + colorTable[i].flags = DoGreen; + ++i; + } + + for (int b = 0; b < d->b_max; ++b) { + colorTable[i].blue = (b << 8 | b); + colorTable[i].pixel = b << d->b_shift; + colorTable[i].flags = DoBlue; + ++i; + } + + XStoreColors(X11->display, d->colormap, colorTable.data(), colorTable.count()); +} + +static QXcbColormap **cmaps = 0; + +void QXcbColormap::initialize() +{ + Display *display = X11->display; + const int screens = ScreenCount(display); + + cmaps = new QXcbColormap*[screens]; + + for (int i = 0; i < screens; ++i) { + cmaps[i] = new QXcbColormap; + QXcbColormapPrivate * const d = cmaps[i]->d; + + bool use_stdcmap = false; + int color_count = X11->color_count; + + // defaults + d->depth = DefaultDepth(display, i); + d->colormap = DefaultColormap(display, i); + d->defaultColormap = true; + d->visual = DefaultVisual(display, i); + d->defaultVisual = true; + + Visual *argbVisual = 0; + + if (X11->visual && i == DefaultScreen(display)) { + // only use the outside colormap on the default screen + d->visual = find_visual(display, i, X11->visual->c_class, + XVisualIDFromVisual(X11->visual), + &d->depth, &d->defaultVisual); + } else if ((X11->visual_class != -1 && X11->visual_class >= 0 && X11->visual_class < 6) + || (X11->visual_id != -1)) { + // look for a specific visual or type of visual + d->visual = find_visual(display, i, X11->visual_class, X11->visual_id, + &d->depth, &d->defaultVisual); + } else if (!X11->custom_cmap) { + XStandardColormap *stdcmap = 0; + int ncmaps = 0; + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + int nvi; + XVisualInfo templ; + templ.screen = i; + templ.depth = 32; + templ.c_class = TrueColor; + XVisualInfo *xvi = XGetVisualInfo(X11->display, VisualScreenMask | + VisualDepthMask | + VisualClassMask, &templ, &nvi); + for (int idx = 0; idx < nvi; ++idx) { + XRenderPictFormat *format = XRenderFindVisualFormat(X11->display, + xvi[idx].visual); + if (format->type == PictTypeDirect && format->direct.alphaMask) { + argbVisual = xvi[idx].visual; + break; + } + } + XFree(xvi); + } +#endif + if (XGetRGBColormaps(display, RootWindow(display, i), + &stdcmap, &ncmaps, XA_RGB_DEFAULT_MAP)) { + if (stdcmap) { + for (int c = 0; c < ncmaps; ++c) { + if (!stdcmap[c].red_max || + !stdcmap[c].green_max || + !stdcmap[c].blue_max || + !stdcmap[c].red_mult || + !stdcmap[c].green_mult || + !stdcmap[c].blue_mult) + continue; // invalid stdcmap + + XVisualInfo proto; + proto.visualid = stdcmap[c].visualid; + proto.screen = i; + + int nvisuals = 0; + XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask | VisualScreenMask, + &proto, &nvisuals); + if (vi) { + if (nvisuals > 0) { + use_stdcmap = true; + + d->mode = ((vi[0].visual->c_class < StaticColor) + ? Gray + : ((vi[0].visual->c_class < TrueColor) + ? Indexed + : Direct)); + + d->depth = vi[0].depth; + d->colormap = stdcmap[c].colormap; + d->defaultColormap = true; + d->visual = vi[0].visual; + d->defaultVisual = (d->visual == DefaultVisual(display, i)); + + d->r_max = stdcmap[c].red_max + 1; + d->g_max = stdcmap[c].green_max + 1; + d->b_max = stdcmap[c].blue_max + 1; + + if (d->mode == Direct) { + // calculate offsets + d->r_shift = lowest_bit(d->visual->red_mask); + d->g_shift = lowest_bit(d->visual->green_mask); + d->b_shift = lowest_bit(d->visual->blue_mask); + } else { + d->r_shift = 0; + d->g_shift = 0; + d->b_shift = 0; + } + } + XFree(vi); + } + break; + } + XFree(stdcmap); + } + } + } + if (!use_stdcmap) { + switch (d->visual->c_class) { + case StaticGray: + d->mode = Gray; + + d->r_max = d->g_max = d->b_max = d->visual->map_entries; + break; + + case XGrayScale: + d->mode = Gray; + + // follow precedent set in libXmu... + if (color_count != 0) + d->r_max = d->g_max = d->b_max = color_count; + else if (d->visual->map_entries > 65000) + d->r_max = d->g_max = d->b_max = 4096; + else if (d->visual->map_entries > 4000) + d->r_max = d->g_max = d->b_max = 512; + else if (d->visual->map_entries > 250) + d->r_max = d->g_max = d->b_max = 12; + else + d->r_max = d->g_max = d->b_max = 4; + break; + + case StaticColor: + d->mode = Indexed; + + d->r_max = right_align(d->visual->red_mask) + 1; + d->g_max = right_align(d->visual->green_mask) + 1; + d->b_max = right_align(d->visual->blue_mask) + 1; + break; + + case PseudoColor: + d->mode = Indexed; + + // follow precedent set in libXmu... + if (color_count != 0) + d->r_max = d->g_max = d->b_max = cube_root(color_count); + else if (d->visual->map_entries > 65000) + d->r_max = d->g_max = d->b_max = 27; + else if (d->visual->map_entries > 4000) + d->r_max = d->g_max = d->b_max = 12; + else if (d->visual->map_entries > 250) + d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries - 125); + else + d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries); + break; + + case TrueColor: + case DirectColor: + d->mode = Direct; + + d->r_max = right_align(d->visual->red_mask) + 1; + d->g_max = right_align(d->visual->green_mask) + 1; + d->b_max = right_align(d->visual->blue_mask) + 1; + + d->r_shift = lowest_bit(d->visual->red_mask); + d->g_shift = lowest_bit(d->visual->green_mask); + d->b_shift = lowest_bit(d->visual->blue_mask); + break; + } + } + + bool ownColormap = false; + if (X11->colormap && i == DefaultScreen(display)) { + // only use the outside colormap on the default screen + d->colormap = X11->colormap; + d->defaultColormap = (d->colormap == DefaultColormap(display, i)); + } else if ((!use_stdcmap + && (((d->visual->c_class & 1) && X11->custom_cmap) + || d->visual != DefaultVisual(display, i))) + || d->visual->c_class == DirectColor) { + // allocate custom colormap (we always do this when using DirectColor visuals) + d->colormap = + XCreateColormap(display, RootWindow(display, i), d->visual, + d->visual->c_class == DirectColor ? AllocAll : AllocNone); + d->defaultColormap = false; + ownColormap = true; + } + + switch (d->mode) { + case Gray: + init_gray(d, i); + break; + case Indexed: + init_indexed(d, i); + break; + case Direct: + init_direct(d, ownColormap); + break; + } + + QX11InfoData *screen = X11->screens + i; + screen->depth = d->depth; + screen->visual = d->visual; + screen->defaultVisual = d->defaultVisual; + screen->colormap = d->colormap; + screen->defaultColormap = d->defaultColormap; + screen->cells = screen->visual->map_entries; + + if (argbVisual) { + X11->argbVisuals[i] = argbVisual; + X11->argbColormaps[i] = XCreateColormap(display, RootWindow(display, i), argbVisual, AllocNone); + } + + // ### + // We assume that 8bpp == pseudocolor, but this is not + // always the case (according to the X server), so we need + // to make sure that our internal data is setup in a way + // that is compatible with our assumptions + if (screen->visual->c_class == TrueColor && screen->depth == 8 && screen->cells == 8) + screen->cells = 256; + } +} + +void QXcbColormap::cleanup() +{ + Display *display = X11->display; + const int screens = ScreenCount(display); + + for (int i = 0; i < screens; ++i) + delete cmaps[i]; + + delete [] cmaps; + cmaps = 0; +} + + +QXcbColormap QXcbColormap::instance(int screen) +{ + if (screen == -1) + screen = QXcbX11Info::appScreen(); + return *cmaps[screen]; +} + +/*! \internal + Constructs a new colormap. +*/ +QXcbColormap::QXcbColormap() + : d(new QXcbColormapPrivate) +{} + +QXcbColormap::QXcbColormap(const QXcbColormap &colormap) + :d (colormap.d) +{ d->ref.ref(); } + +QXcbColormap::~QXcbColormap() +{ + if (!d->ref.deref()) { + if (!d->defaultColormap) + XFreeColormap(X11->display, d->colormap); + delete d; + } +} + +QXcbColormap::Mode QXcbColormap::mode() const +{ return d->mode; } + +int QXcbColormap::depth() const +{ return d->depth; } + +int QXcbColormap::size() const +{ + return (d->mode == Gray + ? d->r_max + : (d->mode == Indexed + ? d->r_max * d->g_max * d->b_max + : -1)); +} + +uint QXcbColormap::pixel(const QColor &color) const +{ + const QRgba64 rgba64 = color.rgba64(); + // XXX We emulate the raster engine here by getting the + // 8-bit values, but we could instead use the 16-bit + // values for slightly better color accuracy + const uint r = (rgba64.red8() * d->r_max) >> 8; + const uint g = (rgba64.green8() * d->g_max) >> 8; + const uint b = (rgba64.blue8() * d->b_max) >> 8; + if (d->mode != Direct) { + if (d->mode == Gray) + return d->pixels.at((r * 30 + g * 59 + b * 11) / 100); + return d->pixels.at(r * d->g_max * d->b_max + g * d->b_max + b); + } + return (r << d->r_shift) + (g << d->g_shift) + (b << d->b_shift); +} + +const QColor QXcbColormap::colorAt(uint pixel) const +{ + if (d->mode != Direct) { + Q_ASSERT(pixel <= (uint)d->colors.size()); + return d->colors.at(pixel); + } + + const int r = (((pixel & d->visual->red_mask) >> d->r_shift) << 8) / d->r_max; + const int g = (((pixel & d->visual->green_mask) >> d->g_shift) << 8) / d->g_max; + const int b = (((pixel & d->visual->blue_mask) >> d->b_shift) << 8) / d->b_max; + return QColor(r, g, b); +} + +const QVector<QColor> QXcbColormap::colormap() const +{ return d->colors; } + +QXcbColormap &QXcbColormap::operator=(const QXcbColormap &colormap) +{ + qAtomicAssign(d, colormap.d); + return *this; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h new file mode 100644 index 0000000000..530e3113e4 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORMAP_X11_H +#define QCOLORMAP_X11_H + +#include <QColor> +#include <QVector> + +QT_BEGIN_NAMESPACE + +class QXcbColormapPrivate; +class QXcbColormap +{ +public: + enum Mode { Direct, Indexed, Gray }; + + static void initialize(); + static void cleanup(); + + static QXcbColormap instance(int screen = -1); + + QXcbColormap(const QXcbColormap &colormap); + ~QXcbColormap(); + + QXcbColormap &operator=(const QXcbColormap &colormap); + + Mode mode() const; + + int depth() const; + int size() const; + + uint pixel(const QColor &color) const; + const QColor colorAt(uint pixel) const; + + const QVector<QColor> colormap() const; + +private: + QXcbColormap(); + QXcbColormapPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QCOLORMAP_X11_H diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp new file mode 100644 index 0000000000..3364b07c08 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp @@ -0,0 +1,2837 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qpixmapcache_p.h> +#include <private/qpaintengine_p.h> +#include <private/qpolygonclipper_p.h> +#include <private/qpainterpath_p.h> +#include <private/qdrawhelper_p.h> +#include <private/qfontengineglyphcache_p.h> + +#if QT_CONFIG(fontconfig) +#include <private/qfontengine_ft_p.h> +#endif + +#include "qpaintengine_x11_p.h" +#include "qtessellator_p.h" +#include "qpixmap_x11_p.h" +#include "qcolormap_x11_p.h" +#include "qt_x11_p.h" +#include "qxcbexport.h" +#include "qxcbnativepainting.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(xrender) + +class QXRenderTessellator : public QTessellator +{ +public: + QXRenderTessellator() : traps(0), allocated(0), size(0) {} + ~QXRenderTessellator() { free(traps); } + XTrapezoid *traps; + int allocated; + int size; + void addTrap(const Trapezoid &trap); + QRect tessellate(const QPointF *points, int nPoints, bool winding) { + size = 0; + setWinding(winding); + return QTessellator::tessellate(points, nPoints).toRect(); + } + void done() { + if (allocated > 64) { + free(traps); + traps = 0; + allocated = 0; + } + } +}; + +void QXRenderTessellator::addTrap(const Trapezoid &trap) +{ + if (size == allocated) { + allocated = qMax(2*allocated, 64); + traps = q_check_ptr((XTrapezoid *)realloc(traps, allocated * sizeof(XTrapezoid))); + } + traps[size].top = Q27Dot5ToXFixed(trap.top); + traps[size].bottom = Q27Dot5ToXFixed(trap.bottom); + traps[size].left.p1.x = Q27Dot5ToXFixed(trap.topLeft->x); + traps[size].left.p1.y = Q27Dot5ToXFixed(trap.topLeft->y); + traps[size].left.p2.x = Q27Dot5ToXFixed(trap.bottomLeft->x); + traps[size].left.p2.y = Q27Dot5ToXFixed(trap.bottomLeft->y); + traps[size].right.p1.x = Q27Dot5ToXFixed(trap.topRight->x); + traps[size].right.p1.y = Q27Dot5ToXFixed(trap.topRight->y); + traps[size].right.p2.x = Q27Dot5ToXFixed(trap.bottomRight->x); + traps[size].right.p2.y = Q27Dot5ToXFixed(trap.bottomRight->y); + ++size; +} + +#endif // QT_CONFIG(xrender) + +class QX11PaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QX11PaintEngine) +public: + QX11PaintEnginePrivate() + { + opacity = 1.0; + scrn = -1; + hd = 0; + picture = 0; + gc = gc_brush = 0; + dpy = 0; + xinfo = 0; + txop = QTransform::TxNone; + has_clipping = false; + render_hints = 0; + xform_scale = 1; +#if QT_CONFIG(xrender) + tessellator = 0; +#endif + } + enum GCMode { + PenGC, + BrushGC + }; + + void init(); + void fillPolygon_translated(const QPointF *points, int pointCount, GCMode gcMode, + QPaintEngine::PolygonDrawMode mode); + void fillPolygon_dev(const QPointF *points, int pointCount, GCMode gcMode, + QPaintEngine::PolygonDrawMode mode); + void fillPath(const QPainterPath &path, GCMode gcmode, bool transform); + void strokePolygon_dev(const QPointF *points, int pointCount, bool close); + void strokePolygon_translated(const QPointF *points, int pointCount, bool close); + void setupAdaptedOrigin(const QPoint &p); + void resetAdaptedOrigin(); + void decidePathFallback() { + use_path_fallback = has_alpha_brush + || has_alpha_pen + || has_custom_pen + || has_complex_xform + || (render_hints & QPainter::Antialiasing); + } + void decideCoordAdjust() { + adjust_coords = !(render_hints & QPainter::Antialiasing) + && (render_hints & QPainter::Qt4CompatiblePainting) + && (has_alpha_pen + || (has_alpha_brush && has_pen && !has_alpha_pen) + || (cpen.style() > Qt::SolidLine)); + } + void clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly); + void systemStateChanged() override; + inline bool isCosmeticPen() const { + if ((render_hints & QPainter::Qt4CompatiblePainting) && cpen == QPen()) + return true; + return cpen.isCosmetic(); + } + + Display *dpy; + int scrn; + int pdev_depth; + unsigned long hd; + QPixmap brush_pm; +#if QT_CONFIG(xrender) + unsigned long picture; + unsigned long current_brush; + QPixmap bitmap_texture; + int composition_mode; +#else + unsigned long picture; +#endif + GC gc; + GC gc_brush; + + QPen cpen; + QBrush cbrush; + QRegion crgn; + QTransform matrix; + qreal opacity; + + uint has_complex_xform : 1; + uint has_scaling_xform : 1; + uint has_non_scaling_xform : 1; + uint has_custom_pen : 1; + uint use_path_fallback : 1; + uint adjust_coords : 1; + uint has_clipping : 1; + uint adapted_brush_origin : 1; + uint adapted_pen_origin : 1; + uint has_pen : 1; + uint has_brush : 1; + uint has_texture : 1; + uint has_alpha_texture : 1; + uint has_pattern : 1; + uint has_alpha_pen : 1; + uint has_alpha_brush : 1; + uint render_hints; + + const QXcbX11Info *xinfo; + QPointF bg_origin; + QTransform::TransformationType txop; + qreal xform_scale; + + struct qt_float_point + { + qreal x, y; + }; + QPolygonClipper<qt_float_point, qt_float_point, float> polygonClipper; + + int xlibMaxLinePoints; +#if QT_CONFIG(xrender) + QXRenderTessellator *tessellator; +#endif +}; + +#if QT_CONFIG(xrender) +class QXRenderGlyphCache : public QFontEngineGlyphCache +{ +public: + QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix); + ~QXRenderGlyphCache(); + + bool addGlyphs(const QTextItemInt &ti, QVarLengthArray<glyph_t> glyphs, QVarLengthArray<QFixedPoint> positions); + bool draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti); + + inline GlyphSet glyphSet(); + inline int glyphBufferSize(const QFontEngineFT::Glyph &glyph) const; + + inline QImage::Format imageFormat() const; + inline const XRenderPictFormat *renderPictFormat() const; + + static inline QFontEngine::GlyphFormat glyphFormatForDepth(QFontEngine *fontEngine, int depth); + static inline Glyph glyphId(glyph_t glyph, QFixed subPixelPosition); + + static inline bool isValidCoordinate(const QFixedPoint &fp); + +private: + QXcbX11Info xinfo; + GlyphSet gset; + QSet<Glyph> cachedGlyphs; +}; +#endif // QT_CONFIG(xrender) + +extern QPixmap qt_pixmapForBrush(int brushStyle, bool invert); //in qbrush.cpp +extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); + +extern "C" { +Q_XCB_EXPORT Drawable qt_x11Handle(const QPaintDevice *pd) +{ + if (!pd) + return 0; + +// if (pd->devType() == QInternal::Widget) +// return static_cast<const QWidget *>(pd)->handle(); + + if (pd->devType() == QInternal::Pixmap) + return qt_x11PixmapHandle(*static_cast<const QPixmap *>(pd)); + + return 0; +} +} + +static const QXcbX11Info *qt_x11Info(const QPaintDevice *pd) +{ + if (!pd) + return 0; + +// if (pd->devType() == QInternal::Widget) +// return &static_cast<const QWidget *>(pd)->x11Info(); + + if (pd->devType() == QInternal::Pixmap) + return &qt_x11Info(*static_cast<const QPixmap *>(pd)); + + return 0; +} + +// use the same rounding as in qrasterizer.cpp (6 bit fixed point) +static const qreal aliasedCoordinateDelta = 0.5 - 0.015625; + +#undef X11 // defined in qt_x11_p.h +extern "C" { +/*! + Returns the X11 specific pen GC for the painter \a p. Note that + QPainter::begin() must be called before this function returns a + valid GC. +*/ +GC Q_XCB_EXPORT qt_x11_get_pen_gc(QPainter *p) +{ + if (p && p->paintEngine() + && p->paintEngine()->isActive() + && p->paintEngine()->type() == QPaintEngine::X11) { + return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc; + } + return 0; +} + +/*! + Returns the X11 specific brush GC for the painter \a p. Note that + QPainter::begin() must be called before this function returns a + valid GC. +*/ +GC Q_XCB_EXPORT qt_x11_get_brush_gc(QPainter *p) +{ + if (p && p->paintEngine() + && p->paintEngine()->isActive() + && p->paintEngine()->type() == QPaintEngine::X11) { + return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc_brush; + } + return 0; +} +} +#define X11 qt_x11Data + +// internal helper. Converts an integer value to an unique string token +template <typename T> + struct HexString +{ + inline HexString(const T t) + : val(t) + {} + + inline void write(QChar *&dest) const + { + const ushort hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + const char *c = reinterpret_cast<const char *>(&val); + for (uint i = 0; i < sizeof(T); ++i) { + *dest++ = hexChars[*c & 0xf]; + *dest++ = hexChars[(*c & 0xf0) >> 4]; + ++c; + } + } + const T val; +}; + +// specialization to enable fast concatenating of our string tokens to a string +template <typename T> + struct QConcatenable<HexString<T> > +{ + typedef HexString<T> type; + enum { ExactSize = true }; + static int size(const HexString<T> &) { return sizeof(T) * 2; } + static inline void appendTo(const HexString<T> &str, QChar *&out) { str.write(out); } + typedef QString ConvertTo; +}; + +#if QT_CONFIG(xrender) +static const int compositionModeToRenderOp[QPainter::CompositionMode_Xor + 1] = { + PictOpOver, //CompositionMode_SourceOver, + PictOpOverReverse, //CompositionMode_DestinationOver, + PictOpClear, //CompositionMode_Clear, + PictOpSrc, //CompositionMode_Source, + PictOpDst, //CompositionMode_Destination, + PictOpIn, //CompositionMode_SourceIn, + PictOpInReverse, //CompositionMode_DestinationIn, + PictOpOut, //CompositionMode_SourceOut, + PictOpOutReverse, //CompositionMode_DestinationOut, + PictOpAtop, //CompositionMode_SourceAtop, + PictOpAtopReverse, //CompositionMode_DestinationAtop, + PictOpXor //CompositionMode_Xor +}; + +static inline int qpainterOpToXrender(QPainter::CompositionMode mode) +{ + Q_ASSERT(mode <= QPainter::CompositionMode_Xor); + return compositionModeToRenderOp[mode]; +} + +static inline bool complexPictOp(int op) +{ + return op != PictOpOver && op != PictOpSrc; +} +#endif + +static inline void x11SetClipRegion(Display *dpy, GC gc, GC gc2, +#if QT_CONFIG(xrender) + Picture picture, +#else + Qt::HANDLE picture, +#endif + const QRegion &r) +{ +// int num; +// XRectangle *rects = (XRectangle *)qt_getClipRects(r, num); + QVector<XRectangle> rects = qt_region_to_xrectangles(r); + int num = rects.size(); + + if (gc) + XSetClipRectangles( dpy, gc, 0, 0, rects.data(), num, Unsorted ); + if (gc2) + XSetClipRectangles( dpy, gc2, 0, 0, rects.data(), num, Unsorted ); + +#if QT_CONFIG(xrender) + if (picture) + XRenderSetPictureClipRectangles(dpy, picture, 0, 0, rects.data(), num); +#else + Q_UNUSED(picture); +#endif // QT_CONFIG(xrender) +} + + +static inline void x11ClearClipRegion(Display *dpy, GC gc, GC gc2, +#if QT_CONFIG(xrender) + Picture picture +#else + Qt::HANDLE picture +#endif + ) +{ + if (gc) + XSetClipMask(dpy, gc, XNone); + if (gc2) + XSetClipMask(dpy, gc2, XNone); + +#if QT_CONFIG(xrender) + if (picture) { + XRenderPictureAttributes attrs; + attrs.clip_mask = XNone; + XRenderChangePicture (dpy, picture, CPClipMask, &attrs); + } +#else + Q_UNUSED(picture); +#endif // QT_CONFIG(xrender) +} + + +#define DITHER_SIZE 16 +static const uchar base_dither_matrix[DITHER_SIZE][DITHER_SIZE] = { + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + +static QPixmap qt_patternForAlpha(uchar alpha, int screen) +{ + QPixmap pm; + QString key = QLatin1Literal("$qt-alpha-brush$") + % HexString<uchar>(alpha) + % HexString<int>(screen); + + if (!QPixmapCache::find(key, pm)) { + // #### why not use a mono image here???? + QImage pattern(DITHER_SIZE, DITHER_SIZE, QImage::Format_ARGB32); + pattern.fill(0xffffffff); + for (int y = 0; y < DITHER_SIZE; ++y) { + for (int x = 0; x < DITHER_SIZE; ++x) { + if (base_dither_matrix[x][y] <= alpha) + pattern.setPixel(x, y, 0x00000000); + } + } + pm = QBitmap::fromImage(pattern); + qt_x11SetScreen(pm, screen); + //pm.x11SetScreen(screen); + QPixmapCache::insert(key, pm); + } + return pm; +} + + +#if QT_CONFIG(xrender) +static Picture getPatternFill(int screen, const QBrush &b) +{ + if (!X11->use_xrender) + return XNone; + + XRenderColor color = X11->preMultiply(b.color()); + XRenderColor bg_color; + + bg_color = X11->preMultiply(QColor(0, 0, 0, 0)); + + for (int i = 0; i < X11->pattern_fill_count; ++i) { + if (X11->pattern_fills[i].screen == screen + && X11->pattern_fills[i].opaque == false + && X11->pattern_fills[i].style == b.style() + && X11->pattern_fills[i].color.alpha == color.alpha + && X11->pattern_fills[i].color.red == color.red + && X11->pattern_fills[i].color.green == color.green + && X11->pattern_fills[i].color.blue == color.blue + && X11->pattern_fills[i].bg_color.alpha == bg_color.alpha + && X11->pattern_fills[i].bg_color.red == bg_color.red + && X11->pattern_fills[i].bg_color.green == bg_color.green + && X11->pattern_fills[i].bg_color.blue == bg_color.blue) + return X11->pattern_fills[i].picture; + } + // none found, replace one + int i = qrand() % 16; + + if (X11->pattern_fills[i].screen != screen && X11->pattern_fills[i].picture) { + XRenderFreePicture (QXcbX11Info::display(), X11->pattern_fills[i].picture); + X11->pattern_fills[i].picture = 0; + } + + if (!X11->pattern_fills[i].picture) { + Pixmap pixmap = XCreatePixmap (QXcbX11Info::display(), RootWindow (QXcbX11Info::display(), screen), 8, 8, 32); + XRenderPictureAttributes attrs; + attrs.repeat = True; + X11->pattern_fills[i].picture = XRenderCreatePicture (QXcbX11Info::display(), pixmap, + XRenderFindStandardFormat(QXcbX11Info::display(), PictStandardARGB32), + CPRepeat, &attrs); + XFreePixmap (QXcbX11Info::display(), pixmap); + } + + X11->pattern_fills[i].screen = screen; + X11->pattern_fills[i].color = color; + X11->pattern_fills[i].bg_color = bg_color; + X11->pattern_fills[i].opaque = false; + X11->pattern_fills[i].style = b.style(); + + XRenderFillRectangle(QXcbX11Info::display(), PictOpSrc, X11->pattern_fills[i].picture, &bg_color, 0, 0, 8, 8); + + QPixmap pattern(qt_pixmapForBrush(b.style(), true)); + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(QXcbX11Info::display(), qt_x11PictureHandle(pattern), CPRepeat, &attrs); + + Picture fill_fg = X11->getSolidFill(screen, b.color()); + XRenderComposite(QXcbX11Info::display(), PictOpOver, fill_fg, qt_x11PictureHandle(pattern), + X11->pattern_fills[i].picture, + 0, 0, 0, 0, 0, 0, 8, 8); + + return X11->pattern_fills[i].picture; +} + +static void qt_render_bitmap(Display *dpy, int scrn, Picture src, Picture dst, + int sx, int sy, int x, int y, int sw, int sh, + const QPen &pen) +{ + Picture fill_fg = X11->getSolidFill(scrn, pen.color()); + XRenderComposite(dpy, PictOpOver, + fill_fg, src, dst, sx, sy, sx, sy, x, y, sw, sh); +} +#endif + +void QX11PaintEnginePrivate::init() +{ + dpy = 0; + scrn = 0; + hd = 0; + picture = 0; + xinfo = 0; +#if QT_CONFIG(xrender) + current_brush = 0; + composition_mode = PictOpOver; + tessellator = new QXRenderTessellator; +#endif +} + +void QX11PaintEnginePrivate::setupAdaptedOrigin(const QPoint &p) +{ + if (adapted_pen_origin) + XSetTSOrigin(dpy, gc, p.x(), p.y()); + if (adapted_brush_origin) + XSetTSOrigin(dpy, gc_brush, p.x(), p.y()); +} + +void QX11PaintEnginePrivate::resetAdaptedOrigin() +{ + if (adapted_pen_origin) + XSetTSOrigin(dpy, gc, 0, 0); + if (adapted_brush_origin) + XSetTSOrigin(dpy, gc_brush, 0, 0); +} + +void QX11PaintEnginePrivate::clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly) +{ + int clipped_count = 0; + qt_float_point *clipped_points = 0; + polygonClipper.clipPolygon((qt_float_point *) poly.data(), poly.size(), + &clipped_points, &clipped_count); + clipped_poly->resize(clipped_count); + for (int i=0; i<clipped_count; ++i) + (*clipped_poly)[i] = *((QPointF *)(&clipped_points[i])); +} + +void QX11PaintEnginePrivate::systemStateChanged() +{ + Q_Q(QX11PaintEngine); + QPainter *painter = q->state ? q->state->painter() : nullptr; + if (painter && painter->hasClipping()) { + if (q->testDirty(QPaintEngine::DirtyTransform)) + q->updateMatrix(q->state->transform()); + QPolygonF clip_poly_dev(matrix.map(painter->clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + q->updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); + } else { + q->updateClipRegion_dev(QRegion(), Qt::NoClip); + } +} + +static QPaintEngine::PaintEngineFeatures qt_decide_features() +{ + QPaintEngine::PaintEngineFeatures features = + QPaintEngine::PrimitiveTransform + | QPaintEngine::PatternBrush + | QPaintEngine::AlphaBlend + | QPaintEngine::PainterPaths + | QPaintEngine::RasterOpModes; + + if (X11->use_xrender) { + features |= QPaintEngine::Antialiasing; + features |= QPaintEngine::PorterDuff; + features |= QPaintEngine::MaskedBrush; +#if 0 + if (X11->xrender_version > 10) { + features |= QPaintEngine::LinearGradientFill; + // ### + } +#endif + } + + return features; +} + +/* + * QX11PaintEngine members + */ + +QX11PaintEngine::QX11PaintEngine() + : QPaintEngine(*(new QX11PaintEnginePrivate), qt_decide_features()) +{ + Q_D(QX11PaintEngine); + d->init(); +} + +QX11PaintEngine::QX11PaintEngine(QX11PaintEnginePrivate &dptr) + : QPaintEngine(dptr, qt_decide_features()) +{ + Q_D(QX11PaintEngine); + d->init(); +} + +QX11PaintEngine::~QX11PaintEngine() +{ +#if QT_CONFIG(xrender) + Q_D(QX11PaintEngine); + delete d->tessellator; +#endif +} + +bool QX11PaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QX11PaintEngine); + d->xinfo = qt_x11Info(pdev); +#if QT_CONFIG(xrender) + if (pdev->devType() == QInternal::Pixmap) { + const QPixmap *pm = static_cast<const QPixmap *>(pdev); + QX11PlatformPixmap *data = static_cast<QX11PlatformPixmap*>(pm->handle()); + if (X11->use_xrender && data->depth() != 32 && data->x11_mask) + data->convertToARGB32(); + d->picture = qt_x11PictureHandle(*static_cast<const QPixmap *>(pdev)); + } +#else + d->picture = 0; +#endif + d->hd = qt_x11Handle(pdev); + + Q_ASSERT(d->xinfo != 0); + d->dpy = d->xinfo->display(); // get display variable + d->scrn = d->xinfo->screen(); // get screen variable + + d->crgn = QRegion(); + d->gc = XCreateGC(d->dpy, d->hd, 0, 0); + d->gc_brush = XCreateGC(d->dpy, d->hd, 0, 0); + d->has_alpha_brush = false; + d->has_alpha_pen = false; + d->has_clipping = false; + d->has_complex_xform = false; + d->has_scaling_xform = false; + d->has_non_scaling_xform = true; + d->xform_scale = 1; + d->has_custom_pen = false; + d->matrix = QTransform(); + d->pdev_depth = d->pdev->depth(); + d->render_hints = 0; + d->txop = QTransform::TxNone; + d->use_path_fallback = false; +#if QT_CONFIG(xrender) + d->composition_mode = PictOpOver; +#endif + d->xlibMaxLinePoints = 32762; // a safe number used to avoid, call to XMaxRequestSize(d->dpy) - 3; + d->opacity = 1; + + // Set up the polygon clipper. Note: This will only work in + // polyline mode as long as we have a buffer zone, since a + // polyline may be clipped into several non-connected polylines. + const int BUFFERZONE = 1000; + QRect devClipRect(-BUFFERZONE, -BUFFERZONE, + pdev->width() + 2*BUFFERZONE, pdev->height() + 2*BUFFERZONE); + d->polygonClipper.setBoundingRect(devClipRect); + + setSystemClip(systemClip()); + d->systemStateChanged(); + + qt_x11SetDefaultScreen(d->xinfo->screen()); + + updatePen(QPen(Qt::black)); + updateBrush(QBrush(Qt::white), QPoint()); + + setDirty(QPaintEngine::DirtyClipRegion); + setDirty(QPaintEngine::DirtyPen); + setDirty(QPaintEngine::DirtyBrush); + setDirty(QPaintEngine::DirtyBackground); + + setActive(true); + return true; +} + +bool QX11PaintEngine::end() +{ + Q_D(QX11PaintEngine); + +#if QT_CONFIG(xrender) + if (d->picture) { + // reset clipping/subwindow mode on our render picture + XRenderPictureAttributes attrs; + attrs.subwindow_mode = ClipByChildren; + attrs.clip_mask = XNone; + XRenderChangePicture(d->dpy, d->picture, CPClipMask|CPSubwindowMode, &attrs); + } +#endif + + if (d->gc_brush && d->pdev->painters < 2) { + XFreeGC(d->dpy, d->gc_brush); + d->gc_brush = 0; + } + + if (d->gc && d->pdev->painters < 2) { + XFreeGC(d->dpy, d->gc); + d->gc = 0; + } + + // Restore system clip for alien widgets painting outside the paint event. +// if (d->pdev->devType() == QInternal::Widget && !static_cast<QWidget *>(d->pdev)->internalWinId()) + d->currentClipDevice = nullptr; + setSystemClip(QRegion()); + + setActive(false); + return true; +} + +static bool clipLine(QLineF *line, const QRect &rect) +{ + qreal x1 = line->x1(); + qreal x2 = line->x2(); + qreal y1 = line->y1(); + qreal y2 = line->y2(); + + qreal left = rect.x(); + qreal right = rect.x() + rect.width() - 1; + qreal top = rect.y(); + qreal bottom = rect.y() + rect.height() - 1; + + enum { Left, Right, Top, Bottom }; + // clip the lines, after cohen-sutherland, see e.g. http://www.nondot.org/~sabre/graphpro/line6.html + int p1 = ((x1 < left) << Left) + | ((x1 > right) << Right) + | ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + int p2 = ((x2 < left) << Left) + | ((x2 > right) << Right) + | ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + + if (p1 & p2) + // completely outside + return false; + + if (p1 | p2) { + qreal dx = x2 - x1; + qreal dy = y2 - y1; + + // clip x coordinates + if (x1 < left) { + y1 += dy/dx * (left - x1); + x1 = left; + } else if (x1 > right) { + y1 -= dy/dx * (x1 - right); + x1 = right; + } + if (x2 < left) { + y2 += dy/dx * (left - x2); + x2 = left; + } else if (x2 > right) { + y2 -= dy/dx * (x2 - right); + x2 = right; + } + p1 = ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + p2 = ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + if (p1 & p2) + return false; + // clip y coordinates + if (y1 < top) { + x1 += dx/dy * (top - y1); + y1 = top; + } else if (y1 > bottom) { + x1 -= dx/dy * (y1 - bottom); + y1 = bottom; + } + if (y2 < top) { + x2 += dx/dy * (top - y2); + y2 = top; + } else if (y2 > bottom) { + x2 -= dx/dy * (y2 - bottom); + y2 = bottom; + } + *line = QLineF(QPointF(x1, y1), QPointF(x2, y2)); + } + return true; +} + +void QX11PaintEngine::drawLines(const QLine *lines, int lineCount) +{ + Q_ASSERT(lines); + Q_ASSERT(lineCount); + Q_D(QX11PaintEngine); + + if (d->has_alpha_brush + || d->has_alpha_pen + || d->has_custom_pen + || (d->cpen.widthF() > 0 && d->has_complex_xform + && !d->has_non_scaling_xform) + || (d->render_hints & QPainter::Antialiasing)) { + for (int i = 0; i < lineCount; ++i) { + QPainterPath path(lines[i].p1()); + path.lineTo(lines[i].p2()); + drawPath(path); + } + return; + } + + if (d->has_pen) { + for (int i = 0; i < lineCount; ++i) { + QLineF linef; + if (d->txop == QTransform::TxNone) { + linef = lines[i]; + } else { + linef = d->matrix.map(QLineF(lines[i])); + } + if (clipLine(&linef, d->polygonClipper.boundingRect())) { + int x1 = qRound(linef.x1() + aliasedCoordinateDelta); + int y1 = qRound(linef.y1() + aliasedCoordinateDelta); + int x2 = qRound(linef.x2() + aliasedCoordinateDelta); + int y2 = qRound(linef.y2() + aliasedCoordinateDelta); + + XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); + } + } + } +} + +void QX11PaintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_ASSERT(lines); + Q_ASSERT(lineCount); + Q_D(QX11PaintEngine); + + if (d->has_alpha_brush + || d->has_alpha_pen + || d->has_custom_pen + || (d->cpen.widthF() > 0 && d->has_complex_xform + && !d->has_non_scaling_xform) + || (d->render_hints & QPainter::Antialiasing)) { + for (int i = 0; i < lineCount; ++i) { + QPainterPath path(lines[i].p1()); + path.lineTo(lines[i].p2()); + drawPath(path); + } + return; + } + + if (d->has_pen) { + for (int i = 0; i < lineCount; ++i) { + QLineF linef = d->matrix.map(lines[i]); + if (clipLine(&linef, d->polygonClipper.boundingRect())) { + int x1 = qRound(linef.x1() + aliasedCoordinateDelta); + int y1 = qRound(linef.y1() + aliasedCoordinateDelta); + int x2 = qRound(linef.x2() + aliasedCoordinateDelta); + int y2 = qRound(linef.y2() + aliasedCoordinateDelta); + + XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); + } + } + } +} + +static inline QLine clipStraightLine(const QRect &clip, const QLine &l) +{ + if (l.p1().x() == l.p2().x()) { + int x = qBound(clip.left(), l.p1().x(), clip.right()); + int y1 = qBound(clip.top(), l.p1().y(), clip.bottom()); + int y2 = qBound(clip.top(), l.p2().y(), clip.bottom()); + + return QLine(x, y1, x, y2); + } else { + Q_ASSERT(l.p1().y() == l.p2().y()); + + int x1 = qBound(clip.left(), l.p1().x(), clip.right()); + int x2 = qBound(clip.left(), l.p2().x(), clip.right()); + int y = qBound(clip.top(), l.p1().y(), clip.bottom()); + + return QLine(x1, y, x2, y); + } +} + +void QX11PaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + Q_D(QX11PaintEngine); + Q_ASSERT(rects); + Q_ASSERT(rectCount); + + if (rectCount != 1 + || d->has_pen + || d->has_alpha_brush + || d->has_complex_xform + || d->has_custom_pen + || d->cbrush.style() != Qt::SolidPattern +#if QT_CONFIG(xrender) + || complexPictOp(d->composition_mode) +#endif + ) + { + QPaintEngine::drawRects(rects, rectCount); + return; + } + + QPoint alignedOffset; + if (d->txop == QTransform::TxTranslate) { + QPointF offset(d->matrix.dx(), d->matrix.dy()); + alignedOffset = offset.toPoint(); + if (offset != QPointF(alignedOffset)) { + QPaintEngine::drawRects(rects, rectCount); + return; + } + } + + const QRectF& r = rects[0]; + QRect alignedRect = r.toAlignedRect(); + if (r != QRectF(alignedRect)) { + QPaintEngine::drawRects(rects, rectCount); + return; + } + alignedRect.translate(alignedOffset); + + QRect clip(d->polygonClipper.boundingRect()); + alignedRect = alignedRect.intersected(clip); + if (alignedRect.isEmpty()) + return; + + // simple-case: + // the rectangle is pixel-aligned + // the fill brush is just a solid non-alpha color + // the painter transform is only integer translation + // ignore: antialiasing and just XFillRectangles directly + XRectangle xrect; + xrect.x = short(alignedRect.x()); + xrect.y = short(alignedRect.y()); + xrect.width = ushort(alignedRect.width()); + xrect.height = ushort(alignedRect.height()); + XFillRectangles(d->dpy, d->hd, d->gc_brush, &xrect, 1); +} + +void QX11PaintEngine::drawRects(const QRect *rects, int rectCount) +{ + Q_D(QX11PaintEngine); + Q_ASSERT(rects); + Q_ASSERT(rectCount); + + if (d->has_alpha_pen + || d->has_complex_xform + || d->has_custom_pen + || (d->render_hints & QPainter::Antialiasing)) + { + for (int i = 0; i < rectCount; ++i) { + QPainterPath path; + path.addRect(rects[i]); + drawPath(path); + } + return; + } + + QRect clip(d->polygonClipper.boundingRect()); + QPoint offset(qRound(d->matrix.dx()), qRound(d->matrix.dy())); +#if QT_CONFIG(xrender) + ::Picture pict = d->picture; + + if (X11->use_xrender && pict && d->has_brush && d->pdev_depth != 1 + && (d->has_texture || d->has_alpha_brush || complexPictOp(d->composition_mode))) + { + XRenderColor xc; + if (!d->has_texture && !d->has_pattern) + xc = X11->preMultiply(d->cbrush.color()); + + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + if (d->has_pen) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + } + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) + continue; + if (d->has_texture || d->has_pattern) { + XRenderComposite(d->dpy, d->composition_mode, d->current_brush, 0, pict, + qRound(r.x() - d->bg_origin.x()), qRound(r.y() - d->bg_origin.y()), + 0, 0, r.x(), r.y(), r.width(), r.height()); + } else { + XRenderFillRectangle(d->dpy, d->composition_mode, pict, &xc, r.x(), r.y(), r.width(), r.height()); + } + if (d->has_pen) + XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); + } + } else +#endif // QT_CONFIG(xrender) + { + if (d->has_brush && d->has_pen) { + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) + continue; + d->setupAdaptedOrigin(r.topLeft()); + XFillRectangle(d->dpy, d->hd, d->gc_brush, r.x(), r.y(), r.width(), r.height()); + XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); + } + d->resetAdaptedOrigin(); + } else { + QVarLengthArray<XRectangle> xrects(rectCount); + int numClipped = rectCount; + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + --numClipped; + if (d->has_pen) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + } + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) { + --numClipped; + continue; + } + xrects[i].x = short(r.x()); + xrects[i].y = short(r.y()); + xrects[i].width = ushort(r.width()); + xrects[i].height = ushort(r.height()); + } + if (numClipped) { + d->setupAdaptedOrigin(rects[0].topLeft()); + if (d->has_brush) + XFillRectangles(d->dpy, d->hd, d->gc_brush, xrects.data(), numClipped); + else if (d->has_pen) + XDrawRectangles(d->dpy, d->hd, d->gc, xrects.data(), numClipped); + d->resetAdaptedOrigin(); + } + } + } +} + +static inline void setCapStyle(int cap_style, GC gc) +{ + ulong mask = GCCapStyle; + XGCValues vals; + vals.cap_style = cap_style; + XChangeGC(QXcbX11Info::display(), gc, mask, &vals); +} + +void QX11PaintEngine::drawPoints(const QPoint *points, int pointCount) +{ + Q_ASSERT(points); + Q_ASSERT(pointCount); + Q_D(QX11PaintEngine); + + if (!d->has_pen) + return; + + // use the same test here as in drawPath to ensure that we don't use the path fallback + // and end up in XDrawLines for pens with width <= 1 + if (d->cpen.widthF() > 1.0f + || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate)) + { + Qt::PenCapStyle capStyle = d->cpen.capStyle(); + if (capStyle == Qt::FlatCap) { + setCapStyle(CapProjecting, d->gc); + d->cpen.setCapStyle(Qt::SquareCap); + } + const QPoint *end = points + pointCount; + while (points < end) { + QPainterPath path; + path.moveTo(*points); + path.lineTo(points->x()+.005, points->y()); + drawPath(path); + ++points; + } + + if (capStyle == Qt::FlatCap) { + setCapStyle(CapButt, d->gc); + d->cpen.setCapStyle(capStyle); + } + return; + } + + static const int BUF_SIZE = 1024; + XPoint xPoints[BUF_SIZE]; + int i = 0, j = 0; + while (i < pointCount) { + while (i < pointCount && j < BUF_SIZE) { + const QPoint &xformed = d->matrix.map(points[i]); + int x = xformed.x(); + int y = xformed.y(); + if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { + xPoints[j].x = x; + xPoints[j].y = y; + ++j; + } + ++i; + } + if (j) + XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); + + j = 0; + } +} + +void QX11PaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_ASSERT(points); + Q_ASSERT(pointCount); + Q_D(QX11PaintEngine); + + if (!d->has_pen) + return; + + // use the same test here as in drawPath to ensure that we don't use the path fallback + // and end up in XDrawLines for pens with width <= 1 + if (d->cpen.widthF() > 1.0f + || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate)) + { + Qt::PenCapStyle capStyle = d->cpen.capStyle(); + if (capStyle == Qt::FlatCap) { + setCapStyle(CapProjecting, d->gc); + d->cpen.setCapStyle(Qt::SquareCap); + } + + const QPointF *end = points + pointCount; + while (points < end) { + QPainterPath path; + path.moveTo(*points); + path.lineTo(points->x() + 0.005, points->y()); + drawPath(path); + ++points; + } + if (capStyle == Qt::FlatCap) { + setCapStyle(CapButt, d->gc); + d->cpen.setCapStyle(capStyle); + } + return; + } + + static const int BUF_SIZE = 1024; + XPoint xPoints[BUF_SIZE]; + int i = 0, j = 0; + while (i < pointCount) { + while (i < pointCount && j < BUF_SIZE) { + const QPointF &xformed = d->matrix.map(points[i]); + int x = qFloor(xformed.x()); + int y = qFloor(xformed.y()); + + if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { + xPoints[j].x = x; + xPoints[j].y = y; + ++j; + } + ++i; + } + if (j) + XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); + + j = 0; + } +} + +QPainter::RenderHints QX11PaintEngine::supportedRenderHints() const +{ +#if QT_CONFIG(xrender) + if (X11->use_xrender) + return QPainter::Antialiasing; +#endif + return QFlag(0); +} + +void QX11PaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QX11PaintEngine); + QPaintEngine::DirtyFlags flags = state.state(); + + + if (flags & DirtyOpacity) { + d->opacity = state.opacity(); + // Force update pen/brush as to get proper alpha colors propagated + flags |= DirtyPen; + flags |= DirtyBrush; + } + + if (flags & DirtyTransform) updateMatrix(state.transform()); + if (flags & DirtyPen) updatePen(state.pen()); + if (flags & (DirtyBrush | DirtyBrushOrigin)) updateBrush(state.brush(), state.brushOrigin()); + if (flags & DirtyFont) updateFont(state.font()); + + if (state.state() & DirtyClipEnabled) { + if (state.isClipEnabled()) { + QPolygonF clip_poly_dev(d->matrix.map(painter()->clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); + } else { + updateClipRegion_dev(QRegion(), Qt::NoClip); + } + } + + if (flags & DirtyClipPath) { + QPolygonF clip_poly_dev(d->matrix.map(state.clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon(), state.clipPath().fillRule()), + state.clipOperation()); + } else if (flags & DirtyClipRegion) { + extern QPainterPath qt_regionToPath(const QRegion ®ion); + QPainterPath clip_path = qt_regionToPath(state.clipRegion()); + QPolygonF clip_poly_dev(d->matrix.map(clip_path.toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), state.clipOperation()); + } + if (flags & DirtyHints) updateRenderHints(state.renderHints()); + if (flags & DirtyCompositionMode) { + int function = GXcopy; + if (state.compositionMode() >= QPainter::RasterOp_SourceOrDestination) { + switch (state.compositionMode()) { + case QPainter::RasterOp_SourceOrDestination: + function = GXor; + break; + case QPainter::RasterOp_SourceAndDestination: + function = GXand; + break; + case QPainter::RasterOp_SourceXorDestination: + function = GXxor; + break; + case QPainter::RasterOp_NotSourceAndNotDestination: + function = GXnor; + break; + case QPainter::RasterOp_NotSourceOrNotDestination: + function = GXnand; + break; + case QPainter::RasterOp_NotSourceXorDestination: + function = GXequiv; + break; + case QPainter::RasterOp_NotSource: + function = GXcopyInverted; + break; + case QPainter::RasterOp_SourceAndNotDestination: + function = GXandReverse; + break; + case QPainter::RasterOp_NotSourceAndDestination: + function = GXandInverted; + break; + default: + function = GXcopy; + } + } +#if QT_CONFIG(xrender) + else { + d->composition_mode = + qpainterOpToXrender(state.compositionMode()); + } +#endif + XSetFunction(X11->display, d->gc, function); + XSetFunction(X11->display, d->gc_brush, function); + } + d->decidePathFallback(); + d->decideCoordAdjust(); +} + +void QX11PaintEngine::updateRenderHints(QPainter::RenderHints hints) +{ + Q_D(QX11PaintEngine); + d->render_hints = hints; + +#if QT_CONFIG(xrender) + if (X11->use_xrender && d->picture) { + XRenderPictureAttributes attrs; + attrs.poly_edge = (hints & QPainter::Antialiasing) ? PolyEdgeSmooth : PolyEdgeSharp; + XRenderChangePicture(d->dpy, d->picture, CPPolyEdge, &attrs); + } +#endif +} + +void QX11PaintEngine::updatePen(const QPen &pen) +{ + Q_D(QX11PaintEngine); + d->cpen = pen; + int cp = CapButt; + int jn = JoinMiter; + int ps = pen.style(); + + if (d->opacity < 1.0) { + QColor c = d->cpen.color(); + c.setAlpha(qRound(c.alpha()*d->opacity)); + d->cpen.setColor(c); + } + + d->has_pen = (ps != Qt::NoPen); + d->has_alpha_pen = (pen.color().alpha() != 255); + + switch (pen.capStyle()) { + case Qt::SquareCap: + cp = CapProjecting; + break; + case Qt::RoundCap: + cp = CapRound; + break; + case Qt::FlatCap: + default: + cp = CapButt; + break; + } + switch (pen.joinStyle()) { + case Qt::BevelJoin: + jn = JoinBevel; + break; + case Qt::RoundJoin: + jn = JoinRound; + break; + case Qt::MiterJoin: + default: + jn = JoinMiter; + break; + } + + d->adapted_pen_origin = false; + + char dashes[10]; // custom pen dashes + int dash_len = 0; // length of dash list + int xStyle = LineSolid; + + /* + We are emulating Windows here. Windows treats cpen.width() == 1 + (or 0) as a very special case. The fudge variable unifies this + case with the general case. + */ + qreal pen_width = pen.widthF(); + int scale = qRound(pen_width < 1 ? 1 : pen_width); + int space = (pen_width < 1 && pen_width > 0 ? 1 : (2 * scale)); + int dot = 1 * scale; + int dash = 4 * scale; + + d->has_custom_pen = false; + + switch (ps) { + case Qt::NoPen: + case Qt::SolidLine: + xStyle = LineSolid; + break; + case Qt::DashLine: + dashes[0] = dash; + dashes[1] = space; + dash_len = 2; + xStyle = LineOnOffDash; + break; + case Qt::DotLine: + dashes[0] = dot; + dashes[1] = space; + dash_len = 2; + xStyle = LineOnOffDash; + break; + case Qt::DashDotLine: + dashes[0] = dash; + dashes[1] = space; + dashes[2] = dot; + dashes[3] = space; + dash_len = 4; + xStyle = LineOnOffDash; + break; + case Qt::DashDotDotLine: + dashes[0] = dash; + dashes[1] = space; + dashes[2] = dot; + dashes[3] = space; + dashes[4] = dot; + dashes[5] = space; + dash_len = 6; + xStyle = LineOnOffDash; + break; + case Qt::CustomDashLine: + d->has_custom_pen = true; + break; + } + + ulong mask = GCForeground | GCBackground | GCGraphicsExposures | GCLineWidth + | GCCapStyle | GCJoinStyle | GCLineStyle; + XGCValues vals; + vals.graphics_exposures = false; + if (d->pdev_depth == 1) { + vals.foreground = qGray(pen.color().rgb()) > 127 ? 0 : 1; + vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; + } else if (d->pdev->devType() == QInternal::Pixmap && d->pdev_depth == 32 + && X11->use_xrender) { + vals.foreground = pen.color().rgba(); + vals.background = QColor(Qt::transparent).rgba(); + } else { + QXcbColormap cmap = QXcbColormap::instance(d->scrn); + vals.foreground = cmap.pixel(pen.color()); + vals.background = cmap.pixel(QColor(Qt::transparent)); + } + + + vals.line_width = qRound(pen.widthF()); + vals.cap_style = cp; + vals.join_style = jn; + vals.line_style = xStyle; + + XChangeGC(d->dpy, d->gc, mask, &vals); + + if (dash_len) { // make dash list + XSetDashes(d->dpy, d->gc, 0, dashes, dash_len); + } + + if (!d->has_clipping) { // if clipping is set the paintevent clip region is merged with the clip region + QRegion sysClip = systemClip(); + if (!sysClip.isEmpty()) + x11SetClipRegion(d->dpy, d->gc, 0, d->picture, sysClip); + else + x11ClearClipRegion(d->dpy, d->gc, 0, d->picture); + } +} + +void QX11PaintEngine::updateBrush(const QBrush &brush, const QPointF &origin) +{ + Q_D(QX11PaintEngine); + d->cbrush = brush; + d->bg_origin = origin; + d->adapted_brush_origin = false; +#if QT_CONFIG(xrender) + d->current_brush = 0; +#endif + if (d->opacity < 1.0) { + QColor c = d->cbrush.color(); + c.setAlpha(qRound(c.alpha()*d->opacity)); + d->cbrush.setColor(c); + } + + int s = FillSolid; + int bs = d->cbrush.style(); + d->has_brush = (bs != Qt::NoBrush); + d->has_pattern = bs >= Qt::Dense1Pattern && bs <= Qt::DiagCrossPattern; + d->has_texture = bs == Qt::TexturePattern; + d->has_alpha_brush = brush.color().alpha() != 255; + d->has_alpha_texture = d->has_texture && d->cbrush.texture().hasAlphaChannel(); + + ulong mask = GCForeground | GCBackground | GCGraphicsExposures + | GCLineStyle | GCCapStyle | GCJoinStyle | GCFillStyle; + XGCValues vals; + vals.graphics_exposures = false; + if (d->pdev_depth == 1) { + vals.foreground = qGray(d->cbrush.color().rgb()) > 127 ? 0 : 1; + vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; + } else if (X11->use_xrender && d->pdev->devType() == QInternal::Pixmap + && d->pdev_depth == 32) { + vals.foreground = d->cbrush.color().rgba(); + vals.background = QColor(Qt::transparent).rgba(); + } else { + QXcbColormap cmap = QXcbColormap::instance(d->scrn); + vals.foreground = cmap.pixel(d->cbrush.color()); + vals.background = cmap.pixel(QColor(Qt::transparent)); + + if (!X11->use_xrender && d->has_brush && !d->has_pattern && !brush.isOpaque()) { + QPixmap pattern = qt_patternForAlpha(brush.color().alpha(), d->scrn); + mask |= GCStipple; + vals.stipple = qt_x11PixmapHandle(pattern); + s = FillStippled; + d->adapted_brush_origin = true; + } + } + vals.cap_style = CapButt; + vals.join_style = JoinMiter; + vals.line_style = LineSolid; + + if (d->has_pattern || d->has_texture) { + if (bs == Qt::TexturePattern) { + d->brush_pm = qt_toX11Pixmap(d->cbrush.texture()); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->brush_pm), CPRepeat, &attrs); + QX11PlatformPixmap *data = static_cast<QX11PlatformPixmap*>(d->brush_pm.handle()); + if (data->mask_picture) + XRenderChangePicture(d->dpy, data->mask_picture, CPRepeat, &attrs); + } +#endif + } else { + d->brush_pm = qt_toX11Pixmap(qt_pixmapForBrush(bs, true)); + } + qt_x11SetScreen(d->brush_pm, d->scrn); + if (d->brush_pm.depth() == 1) { + mask |= GCStipple; + vals.stipple = qt_x11PixmapHandle(d->brush_pm); + s = FillStippled; +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + d->bitmap_texture = QPixmap(d->brush_pm.size()); + d->bitmap_texture.fill(Qt::transparent); + d->bitmap_texture = qt_toX11Pixmap(d->bitmap_texture); + qt_x11SetScreen(d->bitmap_texture, d->scrn); + + ::Picture src = X11->getSolidFill(d->scrn, d->cbrush.color()); + XRenderComposite(d->dpy, PictOpSrc, src, qt_x11PictureHandle(d->brush_pm), + qt_x11PictureHandle(d->bitmap_texture), + 0, 0, d->brush_pm.width(), d->brush_pm.height(), + 0, 0, d->brush_pm.width(), d->brush_pm.height()); + + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->bitmap_texture), CPRepeat, &attrs); + + d->current_brush = qt_x11PictureHandle(d->bitmap_texture); + } +#endif + } else { + mask |= GCTile; +#if QT_CONFIG(xrender) + if (d->pdev_depth == 32 && d->brush_pm.depth() != 32) { + d->brush_pm.detach(); + QX11PlatformPixmap *brushData = static_cast<QX11PlatformPixmap*>(d->brush_pm.handle()); + brushData->convertToARGB32(); + } +#endif + vals.tile = (d->brush_pm.depth() == d->pdev_depth + ? qt_x11PixmapHandle(d->brush_pm) + : static_cast<QX11PlatformPixmap*>(d->brush_pm.handle())->x11ConvertToDefaultDepth()); + s = FillTiled; +#if QT_CONFIG(xrender) + d->current_brush = qt_x11PictureHandle(d->cbrush.texture()); +#endif + } + + mask |= GCTileStipXOrigin | GCTileStipYOrigin; + vals.ts_x_origin = qRound(origin.x()); + vals.ts_y_origin = qRound(origin.y()); + } +#if QT_CONFIG(xrender) + else if (d->has_alpha_brush) { + d->current_brush = X11->getSolidFill(d->scrn, d->cbrush.color()); + } +#endif + + vals.fill_style = s; + XChangeGC(d->dpy, d->gc_brush, mask, &vals); + if (!d->has_clipping) { + QRegion sysClip = systemClip(); + if (!sysClip.isEmpty()) + x11SetClipRegion(d->dpy, d->gc_brush, 0, d->picture, sysClip); + else + x11ClearClipRegion(d->dpy, d->gc_brush, 0, d->picture); + } +} + +void QX11PaintEngine::drawEllipse(const QRectF &rect) +{ + QRect aligned = rect.toAlignedRect(); + if (aligned == rect) + drawEllipse(aligned); + else + QPaintEngine::drawEllipse(rect); +} + +void QX11PaintEngine::drawEllipse(const QRect &rect) +{ + if (rect.isEmpty()) { + drawRects(&rect, 1); + return; + } + + Q_D(QX11PaintEngine); + QRect devclip(SHRT_MIN, SHRT_MIN, SHRT_MAX*2 - 1, SHRT_MAX*2 - 1); + QRect r(rect); + if (d->txop < QTransform::TxRotate) { + r = d->matrix.mapRect(rect); + } else if (d->txop == QTransform::TxRotate && rect.width() == rect.height()) { + QPainterPath path; + path.addEllipse(rect); + r = d->matrix.map(path).boundingRect().toRect(); + } + + if (d->has_alpha_brush || d->has_alpha_pen || d->has_custom_pen || (d->render_hints & QPainter::Antialiasing) + || d->has_alpha_texture || devclip.intersected(r) != r + || (d->has_complex_xform + && !(d->has_non_scaling_xform && rect.width() == rect.height()))) + { + QPainterPath path; + path.addEllipse(rect); + drawPath(path); + return; + } + + int x = r.x(); + int y = r.y(); + int w = r.width(); + int h = r.height(); + if (w < 1 || h < 1) + return; + if (w == 1 && h == 1) { + XDrawPoint(d->dpy, d->hd, d->has_pen ? d->gc : d->gc_brush, x, y); + return; + } + d->setupAdaptedOrigin(rect.topLeft()); + if (d->has_brush) { // draw filled ellipse + XFillArc(d->dpy, d->hd, d->gc_brush, x, y, w, h, 0, 360*64); + if (!d->has_pen) // make smoother outline + XDrawArc(d->dpy, d->hd, d->gc_brush, x, y, w-1, h-1, 0, 360*64); + } + if (d->has_pen) // draw outline + XDrawArc(d->dpy, d->hd, d->gc, x, y, w, h, 0, 360*64); + d->resetAdaptedOrigin(); +} + + + +void QX11PaintEnginePrivate::fillPolygon_translated(const QPointF *polygonPoints, int pointCount, + QX11PaintEnginePrivate::GCMode gcMode, + QPaintEngine::PolygonDrawMode mode) +{ + + QVarLengthArray<QPointF> translated_points(pointCount); + QPointF offset(matrix.dx(), matrix.dy()); + + qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; + if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) + offset += QPointF(aliasedCoordinateDelta, aliasedCoordinateDelta); + + for (int i = 0; i < pointCount; ++i) { + translated_points[i] = polygonPoints[i] + offset; + + translated_points[i].rx() = qRound(translated_points[i].x()) + offs; + translated_points[i].ry() = qRound(translated_points[i].y()) + offs; + } + + fillPolygon_dev(translated_points.data(), pointCount, gcMode, mode); +} + +#if QT_CONFIG(xrender) +static void qt_XRenderCompositeTrapezoids(Display *dpy, + int op, + Picture src, + Picture dst, + _Xconst XRenderPictFormat *maskFormat, + int xSrc, + int ySrc, + const XTrapezoid *traps, int size) +{ + const int MAX_TRAPS = 50000; + while (size) { + int to_draw = size; + if (to_draw > MAX_TRAPS) + to_draw = MAX_TRAPS; + XRenderCompositeTrapezoids(dpy, op, src, dst, + maskFormat, + xSrc, ySrc, + traps, to_draw); + size -= to_draw; + traps += to_draw; + } +} +#endif + +void QX11PaintEnginePrivate::fillPolygon_dev(const QPointF *polygonPoints, int pointCount, + QX11PaintEnginePrivate::GCMode gcMode, + QPaintEngine::PolygonDrawMode mode) +{ + Q_Q(QX11PaintEngine); + + int clippedCount = 0; + qt_float_point *clippedPoints = 0; + +#if QT_CONFIG(xrender) + //can change if we switch to pen if gcMode != BrushGC + bool has_fill_texture = has_texture; + bool has_fill_pattern = has_pattern; + ::Picture src; +#endif + QBrush fill; + GC fill_gc; + if (gcMode == BrushGC) { + fill = cbrush; + fill_gc = gc_brush; +#if QT_CONFIG(xrender) + if (current_brush) + src = current_brush; + else + src = X11->getSolidFill(scrn, fill.color()); +#endif + } else { + fill = QBrush(cpen.brush()); + fill_gc = gc; +#if QT_CONFIG(xrender) + //we use the pens brush + has_fill_texture = (fill.style() == Qt::TexturePattern); + has_fill_pattern = (fill.style() >= Qt::Dense1Pattern && fill.style() <= Qt::DiagCrossPattern); + if (has_fill_texture) + src = qt_x11PictureHandle(fill.texture()); + else if (has_fill_pattern) + src = getPatternFill(scrn, fill); + else + src = X11->getSolidFill(scrn, fill.color()); +#endif + } + + polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, + &clippedPoints, &clippedCount); + +#if QT_CONFIG(xrender) + bool solid_fill = fill.color().alpha() == 255; + if (has_fill_texture && fill.texture().depth() == 1 && solid_fill) { + has_fill_texture = false; + has_fill_pattern = true; + } + + bool antialias = render_hints & QPainter::Antialiasing; + + if (X11->use_xrender + && picture + && !has_fill_pattern + && (clippedCount > 0) + && (fill.style() != Qt::NoBrush) + && ((has_fill_texture && fill.texture().hasAlpha()) || antialias || !solid_fill || has_alpha_pen != has_alpha_brush)) + { + tessellator->tessellate((QPointF *)clippedPoints, clippedCount, + mode == QPaintEngine::WindingMode); + if (tessellator->size > 0) { + XRenderPictureAttributes attrs; + attrs.poly_edge = antialias ? PolyEdgeSmooth : PolyEdgeSharp; + XRenderChangePicture(dpy, picture, CPPolyEdge, &attrs); + int x_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.x) - bg_origin.x()); + int y_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.y) - bg_origin.y()); + qt_XRenderCompositeTrapezoids(dpy, composition_mode, src, picture, + antialias + ? XRenderFindStandardFormat(dpy, PictStandardA8) + : XRenderFindStandardFormat(dpy, PictStandardA1), + x_offset, y_offset, + tessellator->traps, tessellator->size); + tessellator->done(); + } + } else +#endif + if (fill.style() != Qt::NoBrush) { + if (clippedCount > 200000) { + QPolygon poly; + for (int i = 0; i < clippedCount; ++i) + poly << QPoint(qFloor(clippedPoints[i].x), qFloor(clippedPoints[i].y)); + + const QRect bounds = poly.boundingRect(); + const QRect aligned = bounds + & QRect(QPoint(), QSize(pdev->width(), pdev->height())); + + QImage img(aligned.size(), QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + QPainter painter(&img); + painter.translate(-aligned.x(), -aligned.y()); + painter.setPen(Qt::NoPen); + painter.setBrush(fill); + if (gcMode == BrushGC) + painter.setBrushOrigin(q->painter()->brushOrigin()); + painter.drawPolygon(poly); + painter.end(); + + q->drawImage(aligned, img, img.rect(), Qt::AutoColor); + } else if (clippedCount > 0) { + QVarLengthArray<XPoint> xpoints(clippedCount); + for (int i = 0; i < clippedCount; ++i) { + xpoints[i].x = qFloor(clippedPoints[i].x); + xpoints[i].y = qFloor(clippedPoints[i].y); + } + if (mode == QPaintEngine::WindingMode) + XSetFillRule(dpy, fill_gc, WindingRule); + setupAdaptedOrigin(QPoint(xpoints[0].x, xpoints[0].y)); + XFillPolygon(dpy, hd, fill_gc, + xpoints.data(), clippedCount, + mode == QPaintEngine::ConvexMode ? Convex : Complex, CoordModeOrigin); + resetAdaptedOrigin(); + if (mode == QPaintEngine::WindingMode) + XSetFillRule(dpy, fill_gc, EvenOddRule); + } + } +} + +void QX11PaintEnginePrivate::strokePolygon_translated(const QPointF *polygonPoints, int pointCount, bool close) +{ + QVarLengthArray<QPointF> translated_points(pointCount); + QPointF offset(matrix.dx(), matrix.dy()); + for (int i = 0; i < pointCount; ++i) + translated_points[i] = polygonPoints[i] + offset; + strokePolygon_dev(translated_points.data(), pointCount, close); +} + +void QX11PaintEnginePrivate::strokePolygon_dev(const QPointF *polygonPoints, int pointCount, bool close) +{ + int clippedCount = 0; + qt_float_point *clippedPoints = 0; + polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, + &clippedPoints, &clippedCount, close); + + if (clippedCount > 0) { + QVarLengthArray<XPoint> xpoints(clippedCount); + for (int i = 0; i < clippedCount; ++i) { + xpoints[i].x = qRound(clippedPoints[i].x + aliasedCoordinateDelta); + xpoints[i].y = qRound(clippedPoints[i].y + aliasedCoordinateDelta); + } + uint numberPoints = qMin(clippedCount, xlibMaxLinePoints); + XPoint *pts = xpoints.data(); + XDrawLines(dpy, hd, gc, pts, numberPoints, CoordModeOrigin); + pts += numberPoints; + clippedCount -= numberPoints; + numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); + while (clippedCount) { + XDrawLines(dpy, hd, gc, pts-1, numberPoints+1, CoordModeOrigin); + pts += numberPoints; + clippedCount -= numberPoints; + numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); + } + } +} + +void QX11PaintEngine::drawPolygon(const QPointF *polygonPoints, int pointCount, PolygonDrawMode mode) +{ + Q_D(QX11PaintEngine); + + if (d->use_path_fallback) { + QPainterPath path(polygonPoints[0]); + for (int i = 1; i < pointCount; ++i) + path.lineTo(polygonPoints[i]); + if (mode == PolylineMode) { + QBrush oldBrush = d->cbrush; + d->cbrush = QBrush(Qt::NoBrush); + path.setFillRule(Qt::WindingFill); + drawPath(path); + d->cbrush = oldBrush; + } else { + path.setFillRule(mode == OddEvenMode ? Qt::OddEvenFill : Qt::WindingFill); + path.closeSubpath(); + drawPath(path); + } + return; + } + if (mode != PolylineMode && d->has_brush) + d->fillPolygon_translated(polygonPoints, pointCount, QX11PaintEnginePrivate::BrushGC, mode); + + if (d->has_pen) + d->strokePolygon_translated(polygonPoints, pointCount, mode != PolylineMode); +} + + +void QX11PaintEnginePrivate::fillPath(const QPainterPath &path, QX11PaintEnginePrivate::GCMode gc_mode, bool transform) +{ + qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; + + QPainterPath clippedPath; + QPainterPath clipPath; + clipPath.addRect(polygonClipper.boundingRect()); + + if (transform) + clippedPath = (path*matrix).intersected(clipPath); + else + clippedPath = path.intersected(clipPath); + + QList<QPolygonF> polys = clippedPath.toFillPolygons(); + for (int i = 0; i < polys.size(); ++i) { + QVarLengthArray<QPointF> translated_points(polys.at(i).size()); + + for (int j = 0; j < polys.at(i).size(); ++j) { + translated_points[j] = polys.at(i).at(j); + if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) { + translated_points[j].rx() = qRound(translated_points[j].rx() + aliasedCoordinateDelta) + offs; + translated_points[j].ry() = qRound(translated_points[j].ry() + aliasedCoordinateDelta) + offs; + } + } + + fillPolygon_dev(translated_points.data(), polys.at(i).size(), gc_mode, + path.fillRule() == Qt::OddEvenFill ? QPaintEngine::OddEvenMode : QPaintEngine::WindingMode); + } +} + +void QX11PaintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QX11PaintEngine); + if (path.isEmpty()) + return; + + if (d->has_brush) + d->fillPath(path, QX11PaintEnginePrivate::BrushGC, true); + if (d->has_pen + && ((X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate + && !d->has_non_scaling_xform) + || (d->cpen.style() == Qt::CustomDashLine))) { + QPainterPathStroker stroker; + if (d->cpen.style() == Qt::CustomDashLine) { + stroker.setDashPattern(d->cpen.dashPattern()); + stroker.setDashOffset(d->cpen.dashOffset()); + } else { + stroker.setDashPattern(d->cpen.style()); + } + stroker.setCapStyle(d->cpen.capStyle()); + stroker.setJoinStyle(d->cpen.joinStyle()); + QPainterPath stroke; + qreal width = d->cpen.widthF(); + QPolygonF poly; + QRectF deviceRect(0, 0, d->pdev->width(), d->pdev->height()); + // necessary to get aliased alphablended primitives to be drawn correctly + if (d->isCosmeticPen() || d->has_scaling_xform) { + if (d->isCosmeticPen()) + stroker.setWidth(width == 0 ? 1 : width); + else + stroker.setWidth(width * d->xform_scale); + stroker.d_ptr->stroker.setClipRect(deviceRect); + stroke = stroker.createStroke(path * d->matrix); + if (stroke.isEmpty()) + return; + stroke.setFillRule(Qt::WindingFill); + d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, false); + } else { + stroker.setWidth(width); + stroker.d_ptr->stroker.setClipRect(d->matrix.inverted().mapRect(deviceRect)); + stroke = stroker.createStroke(path); + if (stroke.isEmpty()) + return; + stroke.setFillRule(Qt::WindingFill); + d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, true); + } + } else if (d->has_pen) { + // if we have a cosmetic pen - use XDrawLine() for speed + QList<QPolygonF> polys = path.toSubpathPolygons(d->matrix); + for (int i = 0; i < polys.size(); ++i) + d->strokePolygon_dev(polys.at(i).data(), polys.at(i).size(), false); + } +} + +Q_GUI_EXPORT void qt_x11_drawImage(const QRect &rect, const QPoint &pos, const QImage &image, + Drawable hd, GC gc, Display *dpy, Visual *visual, int depth) +{ + Q_ASSERT(image.format() == QImage::Format_RGB32); + Q_ASSERT(image.depth() == 32); + + XImage *xi; + // Note: this code assumes either RGB or BGR, 8 bpc server layouts + const uint red_mask = (uint) visual->red_mask; + bool bgr_layout = (red_mask == 0xff); + + const int w = rect.width(); + const int h = rect.height(); + + QImage im; + int image_byte_order = ImageByteOrder(QXcbX11Info::display()); + if ((QSysInfo::ByteOrder == QSysInfo::BigEndian && ((image_byte_order == LSBFirst) || bgr_layout)) + || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) + || (image_byte_order == LSBFirst && bgr_layout)) + { + im = image.copy(rect); + const int iw = im.bytesPerLine() / 4; + uint *data = (uint *)im.bits(); + for (int i=0; i < h; i++) { + uint *p = data; + uint *end = p + w; + if (bgr_layout && image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + while (p < end) { + *p = ((*p << 8) & 0xffffff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } else if ((image_byte_order == LSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) + || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian)) { + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } else if ((image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) + || (image_byte_order == LSBFirst && bgr_layout)) + { + while (p < end) { + *p = ((*p << 16) & 0x00ff0000) | ((*p >> 16) & 0x000000ff) + | ((*p ) & 0xff00ff00); + p++; + } + } + data += iw; + } + xi = XCreateImage(dpy, visual, depth, ZPixmap, + 0, (char *) im.bits(), w, h, 32, im.bytesPerLine()); + } else { + xi = XCreateImage(dpy, visual, depth, ZPixmap, + 0, (char *) image.scanLine(rect.y())+rect.x()*sizeof(uint), w, h, 32, image.bytesPerLine()); + } + XPutImage(dpy, hd, gc, xi, 0, 0, pos.x(), pos.y(), w, h); + xi->data = 0; // QImage owns these bits + XDestroyImage(xi); +} + +void QX11PaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags) +{ + Q_D(QX11PaintEngine); + + if (image.format() == QImage::Format_RGB32 + && d->pdev_depth >= 24 && image.depth() == 32 + && r.size() == sr.size()) + { + int sx = qRound(sr.x()); + int sy = qRound(sr.y()); + int x = qRound(r.x()); + int y = qRound(r.y()); + int w = qRound(r.width()); + int h = qRound(r.height()); + + qt_x11_drawImage(QRect(sx, sy, w, h), QPoint(x, y), image, d->hd, d->gc, d->dpy, + (Visual *)d->xinfo->visual(), d->pdev_depth); + } else { + QPaintEngine::drawImage(r, image, sr, flags); + } +} + +void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRectF &_sr) +{ + Q_D(QX11PaintEngine); + QRectF sr = _sr; + int x = qRound(r.x()); + int y = qRound(r.y()); + int sx = qRound(sr.x()); + int sy = qRound(sr.y()); + int sw = qRound(sr.width()); + int sh = qRound(sr.height()); + + QPixmap pixmap = qt_toX11Pixmap(px); + if (pixmap.isNull()) + return; + + if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen()) + || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) { + qt_x11SetScreen(pixmap, d->xinfo ? d->xinfo->screen() : DefaultScreen(X11->display)); + } + + qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen()); + +#if QT_CONFIG(xrender) + ::Picture src_pict = qt_x11PictureHandle(pixmap); + if (src_pict && d->picture) { + const int pDepth = pixmap.depth(); + if (pDepth == 1 && (d->has_alpha_pen)) { + qt_render_bitmap(d->dpy, d->scrn, src_pict, d->picture, + sx, sy, x, y, sw, sh, d->cpen); + return; + } else if (pDepth != 1 && (pDepth == 32 || pDepth != d->pdev_depth)) { + XRenderComposite(d->dpy, d->composition_mode, + src_pict, 0, d->picture, sx, sy, 0, 0, x, y, sw, sh); + return; + } + } +#endif + + bool mono_src = pixmap.depth() == 1; + bool mono_dst = d->pdev_depth == 1; + bool restore_clip = false; + + if (static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask) { // pixmap has a mask + QBitmap comb(sw, sh); + GC cgc = XCreateGC(d->dpy, qt_x11PixmapHandle(comb), 0, 0); + XSetForeground(d->dpy, cgc, 0); + XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh); + XSetBackground(d->dpy, cgc, 0); + XSetForeground(d->dpy, cgc, 1); + if (!d->crgn.isEmpty()) { + QVector<XRectangle> rects = qt_region_to_xrectangles(d->crgn); + XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted); + } else if (d->has_clipping) { + XSetClipRectangles(d->dpy, cgc, 0, 0, 0, 0, Unsorted); + } + XSetFillStyle(d->dpy, cgc, FillOpaqueStippled); + XSetTSOrigin(d->dpy, cgc, -sx, -sy); + XSetStipple(d->dpy, cgc, + static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask); + XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh); + XFreeGC(d->dpy, cgc); + + XSetClipOrigin(d->dpy, d->gc, x, y); + XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(comb)); + restore_clip = true; + } + + if (mono_src) { + if (!d->crgn.isEmpty()) { + Pixmap comb = XCreatePixmap(d->dpy, d->hd, sw, sh, 1); + GC cgc = XCreateGC(d->dpy, comb, 0, 0); + XSetForeground(d->dpy, cgc, 0); + XFillRectangle(d->dpy, comb, cgc, 0, 0, sw, sh); + QVector<XRectangle> rects = qt_region_to_xrectangles(d->crgn); + XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted); + XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), comb, cgc, sx, sy, sw, sh, 0, 0); + XFreeGC(d->dpy, cgc); + + XSetClipMask(d->dpy, d->gc, comb); + XSetClipOrigin(d->dpy, d->gc, x, y); + XFreePixmap(d->dpy, comb); + } else { + XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(pixmap)); + XSetClipOrigin(d->dpy, d->gc, x - sx, y - sy); + } + + if (mono_dst) { + XSetForeground(d->dpy, d->gc, qGray(d->cpen.color().rgb()) > 127 ? 0 : 1); + } else { + QXcbColormap cmap = QXcbColormap::instance(d->scrn); + XSetForeground(d->dpy, d->gc, cmap.pixel(d->cpen.color())); + } + XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh); + restore_clip = true; + } else if (mono_dst && !mono_src) { + QBitmap bitmap(pixmap); + XCopyArea(d->dpy, qt_x11PixmapHandle(bitmap), d->hd, d->gc, sx, sy, sw, sh, x, y); + } else { + XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), d->hd, d->gc, sx, sy, sw, sh, x, y); + } + + if (d->pdev->devType() == QInternal::Pixmap) { + const QPixmap *px = static_cast<const QPixmap*>(d->pdev); + Pixmap src_mask = static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask; + Pixmap dst_mask = static_cast<QX11PlatformPixmap*>(px->handle())->x11_mask; + if (dst_mask) { + GC cgc = XCreateGC(d->dpy, dst_mask, 0, 0); + XSetClipOrigin(d->dpy, cgc, x, y); + XSetClipMask(d->dpy, cgc, src_mask); + if (src_mask) { // copy src mask into dst mask + XCopyArea(d->dpy, src_mask, dst_mask, cgc, sx, sy, sw, sh, x, y); + } else { // no src mask, but make sure the area copied is opaque in dest + XSetBackground(d->dpy, cgc, 0); + XSetForeground(d->dpy, cgc, 1); + XFillRectangle(d->dpy, dst_mask, cgc, x, y, sw, sh); + } + XFreeGC(d->dpy, cgc); + } + } + + if (restore_clip) { + XSetClipOrigin(d->dpy, d->gc, 0, 0); + QVector<XRectangle> rects = qt_region_to_xrectangles(d->crgn); + if (rects.isEmpty()) + XSetClipMask(d->dpy, d->gc, XNone); + else + XSetClipRectangles(d->dpy, d->gc, 0, 0, rects.data(), rects.size(), Unsorted); + } +} + +void QX11PaintEngine::updateMatrix(const QTransform &mtx) +{ + Q_D(QX11PaintEngine); + d->txop = mtx.type(); + d->matrix = mtx; + + d->has_complex_xform = (d->txop > QTransform::TxTranslate); + + extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); + bool scaling = qt_scaleForTransform(d->matrix, &d->xform_scale); + d->has_scaling_xform = scaling && d->xform_scale != 1.0; + d->has_non_scaling_xform = scaling && d->xform_scale == 1.0; +} + +/* + NB! the clip region is expected to be in dev coordinates +*/ +void QX11PaintEngine::updateClipRegion_dev(const QRegion &clipRegion, Qt::ClipOperation op) +{ + Q_D(QX11PaintEngine); + QRegion sysClip = systemClip(); + if (op == Qt::NoClip) { + d->has_clipping = false; + d->crgn = sysClip; + if (!sysClip.isEmpty()) { + x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, sysClip); + } else { + x11ClearClipRegion(d->dpy, d->gc, d->gc_brush, d->picture); + } + return; + } + + switch (op) { + case Qt::IntersectClip: + if (d->has_clipping) { + d->crgn &= clipRegion; + break; + } + // fall through + case Qt::ReplaceClip: + if (!sysClip.isEmpty()) + d->crgn = clipRegion.intersected(sysClip); + else + d->crgn = clipRegion; + break; +// case Qt::UniteClip: +// d->crgn |= clipRegion; +// if (!sysClip.isEmpty()) +// d->crgn = d->crgn.intersected(sysClip); +// break; + default: + break; + } + d->has_clipping = true; + x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, d->crgn); +} + +void QX11PaintEngine::updateFont(const QFont &) +{ +} + +Drawable QX11PaintEngine::handle() const +{ + Q_D(const QX11PaintEngine); + Q_ASSERT(isActive()); + Q_ASSERT(d->hd); + return d->hd; +} + +extern void qt_draw_tile(QPaintEngine *, qreal, qreal, qreal, qreal, const QPixmap &, + qreal, qreal); + +void QX11PaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p) +{ + int x = qRound(r.x()); + int y = qRound(r.y()); + int w = qRound(r.width()); + int h = qRound(r.height()); + int sx = qRound(p.x()); + int sy = qRound(p.y()); + + bool mono_src = pixmap.depth() == 1; + Q_D(QX11PaintEngine); + + if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen()) + || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) { + QPixmap* p = const_cast<QPixmap *>(&pixmap); + qt_x11SetScreen(*p, d->xinfo ? d->xinfo->screen() : DefaultScreen(QXcbX11Info::display())); + } + + qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen()); + +#if QT_CONFIG(xrender) + if (X11->use_xrender && d->picture && qt_x11PictureHandle(pixmap)) { +#if 0 + // ### Qt 5: enable this + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, pixmap.x11PictureHandle(), CPRepeat, &attrs); + + if (mono_src) { + qt_render_bitmap(d->dpy, d->scrn, pixmap.x11PictureHandle(), d->picture, + sx, sy, x, y, w, h, d->cpen); + } else { + XRenderComposite(d->dpy, d->composition_mode, + pixmap.x11PictureHandle(), XNone, d->picture, + sx, sy, 0, 0, x, y, w, h); + } +#else + const int numTiles = (w / pixmap.width()) * (h / pixmap.height()); + if (numTiles < 100) { + // this is essentially qt_draw_tile(), inlined for + // the XRenderComposite call + int yPos, xPos, drawH, drawW, yOff, xOff; + yPos = y; + yOff = sy; + while (yPos < y + h) { + drawH = pixmap.height() - yOff; // Cropping first row + if (yPos + drawH > y + h) // Cropping last row + drawH = y + h - yPos; + xPos = x; + xOff = sx; + while (xPos < x + w) { + drawW = pixmap.width() - xOff; // Cropping first column + if (xPos + drawW > x + w) // Cropping last column + drawW = x + w - xPos; + if (mono_src) { + qt_render_bitmap(d->dpy, d->scrn, qt_x11PictureHandle(pixmap), d->picture, + xOff, yOff, xPos, yPos, drawW, drawH, d->cpen); + } else { + XRenderComposite(d->dpy, d->composition_mode, + qt_x11PictureHandle(pixmap), XNone, d->picture, + xOff, yOff, 0, 0, xPos, yPos, drawW, drawH); + } + xPos += drawW; + xOff = 0; + } + yPos += drawH; + yOff = 0; + } + } else { + w = qMin(w, d->pdev->width() - x); + h = qMin(h, d->pdev->height() - y); + if (w <= 0 || h <= 0) + return; + + const int pw = w + sx; + const int ph = h + sy; + QPixmap pm(pw, ph); + if (pixmap.hasAlpha() || mono_src) + pm.fill(Qt::transparent); + + const int mode = pixmap.hasAlpha() ? PictOpOver : PictOpSrc; + const ::Picture pmPicture = qt_x11PictureHandle(pm); + + // first tile + XRenderComposite(d->dpy, mode, + qt_x11PictureHandle(pixmap), XNone, pmPicture, + 0, 0, 0, 0, 0, 0, qMin(pw, pixmap.width()), qMin(ph, pixmap.height())); + + // first row of tiles + int xPos = pixmap.width(); + const int sh = qMin(ph, pixmap.height()); + while (xPos < pw) { + const int sw = qMin(xPos, pw - xPos); + XRenderComposite(d->dpy, mode, + pmPicture, XNone, pmPicture, + 0, 0, 0, 0, xPos, 0, sw, sh); + xPos *= 2; + } + + // remaining rows + int yPos = pixmap.height(); + const int sw = pw; + while (yPos < ph) { + const int sh = qMin(yPos, ph - yPos); + XRenderComposite(d->dpy, mode, + pmPicture, XNone, pmPicture, + 0, 0, 0, 0, 0, yPos, sw, sh); + yPos *= 2; + } + + // composite + if (mono_src) + qt_render_bitmap(d->dpy, d->scrn, pmPicture, d->picture, + sx, sy, x, y, w, h, d->cpen); + else + XRenderComposite(d->dpy, d->composition_mode, + pmPicture, XNone, d->picture, + sx, sy, 0, 0, x, y, w, h); + } +#endif + } else +#endif // QT_CONFIG(xrender) + if (pixmap.depth() > 1 && !static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask) { + XSetTile(d->dpy, d->gc, qt_x11PixmapHandle(pixmap)); + XSetFillStyle(d->dpy, d->gc, FillTiled); + XSetTSOrigin(d->dpy, d->gc, x-sx, y-sy); + XFillRectangle(d->dpy, d->hd, d->gc, x, y, w, h); + XSetTSOrigin(d->dpy, d->gc, 0, 0); + XSetFillStyle(d->dpy, d->gc, FillSolid); + } else { + qt_draw_tile(this, x, y, w, h, pixmap, sx, sy); + } +} + +bool QX11PaintEngine::drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti) +{ +#if QT_CONFIG(xrender) + Q_D(QX11PaintEngine); + Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); + + if (!X11->use_xrender) + return false; + + QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform); + + if (!set || set->outline_drawing) + return false; + + QFontEngine::GlyphFormat glyphFormat = QXRenderGlyphCache::glyphFormatForDepth(ft, d->pdev_depth); + + QXRenderGlyphCache *cache = static_cast<QXRenderGlyphCache *>(ft->glyphCache(set, glyphFormat, transform)); + if (!cache) { + cache = new QXRenderGlyphCache(QXcbX11Info(), glyphFormat, transform); + ft->setGlyphCache(set, cache); + } + + return cache->draw(X11->getSolidFill(d->scrn, d->cpen.color()), d->picture, transform, ti); +#else // !QT_CONFIG(xrender) + return false; +#endif // QT_CONFIG(xrender) +} + +void QX11PaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(QX11PaintEngine); + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + + switch (ti.fontEngine->type()) { + case QFontEngine::TestFontEngine: + case QFontEngine::Box: + d->drawBoxTextItem(p, ti); + break; +#if QT_CONFIG(fontconfig) + case QFontEngine::Freetype: + drawFreetype(p, ti); + break; +#endif + default: + Q_ASSERT(false); + } +} + +#if QT_CONFIG(fontconfig) +static bool path_for_glyphs(QPainterPath *path, + const QVarLengthArray<glyph_t> &glyphs, + const QVarLengthArray<QFixedPoint> &positions, + const QFontEngineFT *ft) +{ + bool result = true; + *path = QPainterPath(); + path->setFillRule(Qt::WindingFill); + ft->lockFace(); + int i = 0; + while (i < glyphs.size()) { + QFontEngineFT::Glyph *glyph = ft->loadGlyph(glyphs[i], 0, QFontEngineFT::Format_Mono); + // #### fix case where we don't get a glyph + if (!glyph || glyph->format != QFontEngineFT::Format_Mono) { + result = false; + break; + } + + int n = 0; + int h = glyph->height; + int xp = qRound(positions[i].x); + int yp = qRound(positions[i].y); + + xp += glyph->x; + yp += -glyph->y + glyph->height; + int pitch = ((glyph->width + 31) & ~31) >> 3; + + uchar *src = glyph->data; + while (h--) { + for (int x = 0; x < glyph->width; ++x) { + bool set = src[x >> 3] & (0x80 >> (x & 7)); + if (set) { + QRect r(xp + x, yp - h, 1, 1); + while (x+1 < glyph->width && src[(x+1) >> 3] & (0x80 >> ((x+1) & 7))) { + ++x; + r.setRight(r.right()+1); + } + + path->addRect(r); + ++n; + } + } + src += pitch; + } + ++i; + } + ft->unlockFace(); + return result; +} + +void QX11PaintEngine::drawFreetype(const QPointF &p, const QTextItemInt &ti) +{ + Q_D(QX11PaintEngine); + + if (!ti.glyphs.numGlyphs) + return; + + if (!d->cpen.isSolid()) { + QPaintEngine::drawTextItem(p, ti); + return; + } + + const bool xrenderPath = (X11->use_xrender + && !(d->pdev->devType() == QInternal::Pixmap + && static_cast<const QPixmap *>(d->pdev)->handle()->pixelType() == QPlatformPixmap::BitmapType)); + + if (xrenderPath) { + QTransform transform = d->matrix; + transform.translate(p.x(), p.y()); + + if (drawCachedGlyphs(transform, ti)) + return; + } + + QTransform transform; + transform.translate(p.x(), p.y()); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + ti.fontEngine->getGlyphPositions(ti.glyphs, transform, ti.flags, glyphs, positions); + + if (glyphs.count() == 0) + return; + + QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform); + QPainterPath path; + + if (!set || set->outline_drawing || !path_for_glyphs(&path, glyphs, positions, ft)) { + QPaintEngine::drawTextItem(p, ti); + return; + } + + if (path.elementCount() <= 1) + return; + + Q_ASSERT((path.elementCount() % 5) == 0); + if (d->txop >= QTransform::TxScale) { + painter()->save(); + painter()->setBrush(d->cpen.brush()); + painter()->setPen(Qt::NoPen); + painter()->drawPath(path); + painter()->restore(); + return; + } + + const int rectcount = 256; + XRectangle rects[rectcount]; + int num_rects = 0; + + QPoint delta(qRound(d->matrix.dx()), qRound(d->matrix.dy())); + QRect clip(d->polygonClipper.boundingRect()); + for (int i=0; i < path.elementCount(); i+=5) { + int x = qRound(path.elementAt(i).x); + int y = qRound(path.elementAt(i).y); + int w = qRound(path.elementAt(i+1).x) - x; + int h = qRound(path.elementAt(i+2).y) - y; + + QRect rect = QRect(x + delta.x(), y + delta.y(), w, h); + rect = rect.intersected(clip); + if (rect.isEmpty()) + continue; + + rects[num_rects].x = short(rect.x()); + rects[num_rects].y = short(rect.y()); + rects[num_rects].width = ushort(rect.width()); + rects[num_rects].height = ushort(rect.height()); + ++num_rects; + if (num_rects == rectcount) { + XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); + num_rects = 0; + } + } + if (num_rects > 0) + XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); +} +#endif // QT_CONFIG(fontconfig) + +#if QT_CONFIG(xrender) +QXRenderGlyphCache::QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix) + : QFontEngineGlyphCache(format, matrix) + , xinfo(x) + , gset(XNone) +{} + +QXRenderGlyphCache::~QXRenderGlyphCache() +{ + if (gset != XNone) + XRenderFreeGlyphSet(xinfo.display(), gset); +} + +bool QXRenderGlyphCache::addGlyphs(const QTextItemInt &ti, QVarLengthArray<glyph_t> glyphs, QVarLengthArray<QFixedPoint> positions) +{ + Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); + + QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform()); + + XGlyphInfo xglyphinfo; + + for (int i = 0; i < glyphs.size(); ++i) { + const QFixed spp = ft->subPixelPositionForX(positions[i].x); + QFontEngineFT::Glyph *glyph = set->getGlyph(glyphs[i], spp); + Glyph xglyphid = qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyphs[i], spp)); + + if (glyph && glyph->format == glyphFormat()) { + if (cachedGlyphs.contains(xglyphid)) { + continue; + } else { + set->setGlyph(glyphs[i], spp, nullptr); + delete glyph; + glyph = 0; + } + } + + glyph = ft->loadGlyphFor(glyphs[i], spp, glyphFormat(), transform()); + + if (glyph == 0 || glyph->format != glyphFormat()) + return false; + + set->setGlyph(glyphs[i], spp, glyph); + Q_ASSERT(glyph->data || glyph->width == 0 || glyph->height == 0); + + xglyphinfo.width = glyph->width; + xglyphinfo.height = glyph->height; + xglyphinfo.x = -glyph->x; + xglyphinfo.y = glyph->y; + xglyphinfo.xOff = glyph->advance; + xglyphinfo.yOff = 0; + + XRenderAddGlyphs(xinfo.display(), glyphSet(), &xglyphid, &xglyphinfo, 1, (const char *) glyph->data, glyphBufferSize(*glyph)); + cachedGlyphs.insert(xglyphid); + } + + return true; +} + +bool QXRenderGlyphCache::draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti) +{ + Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); + + if (ti.glyphs.numGlyphs == 0) + return true; + + QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(matrix); + + QVarLengthArray<glyph_t> glyphs; + QVarLengthArray<QFixedPoint> positions; + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + if (glyphs.isEmpty()) + return true; + + if (!addGlyphs(ti, glyphs, positions)) + return false; + + QVarLengthArray<unsigned int> chars(glyphs.size()); + + for (int i = 0; i < glyphs.size(); ++i) + chars[i] = glyphId(glyphs[i], ft->subPixelPositionForX(positions[i].x)); + + int i = 0; + while (i < glyphs.size() && !isValidCoordinate(positions[i])) + ++i; + + if (i >= glyphs.size()) + return true; + + QFixed xp = positions[i].x; + QFixed yp = positions[i].y; + QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); + + XGlyphElt32 elt; + elt.glyphset = gset; + elt.chars = &chars[i]; + elt.nchars = 1; + elt.xOff = qRound(xp + offs); + elt.yOff = qRound(yp + offs); + + ++i; + + for (; i < glyphs.size(); ++i) { + if (!isValidCoordinate(positions[i])) + break; + + const QFixed spp = ft->subPixelPositionForX(positions[i].x); + QFontEngineFT::Glyph *g = set->getGlyph(glyphs[i], spp); + + if (g + && positions[i].x == xp + g->advance + && positions[i].y == yp + && elt.nchars < 253 // don't draw more than 253 characters as some X servers + // hang with it + ) { + elt.nchars++; + xp += g->advance; + } else { + xp = positions[i].x; + yp = positions[i].y; + + XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst, + renderPictFormat(), 0, 0, 0, 0, + &elt, 1); + elt.chars = &chars[i]; + elt.nchars = 1; + elt.xOff = qRound(xp + offs); + elt.yOff = qRound(yp + offs); + } + } + + XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst, + renderPictFormat(), 0, 0, 0, 0, &elt, 1); + + return true; +} + +GlyphSet QXRenderGlyphCache::glyphSet() +{ + if (gset == XNone) + gset = XRenderCreateGlyphSet(xinfo.display(), renderPictFormat()); + + Q_ASSERT(gset != XNone); + return gset; +} + +int QXRenderGlyphCache::glyphBufferSize(const QFontEngineFT::Glyph &glyph) const +{ + int pitch = 0; + + switch (glyphFormat()) { + case QFontEngine::Format_Mono: + pitch = ((glyph.width + 31) & ~31) >> 3; + break; + case QFontEngine::Format_A8: + pitch = (glyph.width + 3) & ~3; + break; + default: + pitch = glyph.width * 4; + break; + } + + return pitch * glyph.height; +} + +QImage::Format QXRenderGlyphCache::imageFormat() const +{ + switch (glyphFormat()) { + case QFontEngine::Format_None: + Q_UNREACHABLE(); + break; + case QFontEngine::Format_Mono: + return QImage::Format_Mono; + break; + case QFontEngine::Format_A8: + return QImage::Format_Alpha8; + break; + case QFontEngine::Format_A32: + case QFontEngine::Format_ARGB: + return QImage::Format_ARGB32_Premultiplied; + break; + } + + Q_UNREACHABLE(); +} + +const XRenderPictFormat *QXRenderGlyphCache::renderPictFormat() const +{ + switch (glyphFormat()) { + case QFontEngine::Format_None: + Q_UNREACHABLE(); + break; + case QFontEngine::Format_Mono: + return XRenderFindStandardFormat(xinfo.display(), PictStandardA1); + break; + case QFontEngine::Format_A8: + return XRenderFindStandardFormat(xinfo.display(), PictStandardA8); + break; + case QFontEngine::Format_A32: + case QFontEngine::Format_ARGB: + return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32); + break; + } + + Q_UNREACHABLE(); +} + +QFontEngine::GlyphFormat QXRenderGlyphCache::glyphFormatForDepth(QFontEngine *fontEngine, int depth) +{ + QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat; + + if (glyphFormat == QFontEngine::Format_None) { + switch (depth) { + case 32: + glyphFormat = QFontEngine::Format_ARGB; + break; + case 24: + glyphFormat = QFontEngine::Format_A32; + break; + case 1: + glyphFormat = QFontEngine::Format_Mono; + break; + default: + glyphFormat = QFontEngine::Format_A8; + break; + } + } + + return glyphFormat; +} + +Glyph QXRenderGlyphCache::glyphId(glyph_t glyph, QFixed subPixelPosition) +{ + return qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyph, subPixelPosition)); +} + +bool QXRenderGlyphCache::isValidCoordinate(const QFixedPoint &fp) +{ + enum { t_min = SHRT_MIN, t_max = SHRT_MAX }; + return (fp.x < t_min || fp.x > t_max || fp.y < t_min || fp.y > t_max) ? false : true; +} +#endif // QT_CONFIG(xrender) + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h new file mode 100644 index 0000000000..34b5d929d5 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_X11_H +#define QPAINTENGINE_X11_H + +#include <QtGui/QPaintEngine> + +typedef unsigned long XID; +typedef XID Drawable; +typedef struct _XGC *GC; + +QT_BEGIN_NAMESPACE + +extern "C" { +Drawable qt_x11Handle(const QPaintDevice *pd); +GC qt_x11_get_pen_gc(QPainter *); +GC qt_x11_get_brush_gc(QPainter *); +} + +class QX11PaintEnginePrivate; +class QX11PaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QX11PaintEngine) +public: + QX11PaintEngine(); + ~QX11PaintEngine(); + + bool begin(QPaintDevice *pdev) Q_DECL_OVERRIDE; + bool end() Q_DECL_OVERRIDE; + + void updateState(const QPaintEngineState &state) Q_DECL_OVERRIDE; + + void updatePen(const QPen &pen); + void updateBrush(const QBrush &brush, const QPointF &pt); + void updateRenderHints(QPainter::RenderHints hints); + void updateFont(const QFont &font); + void updateMatrix(const QTransform &matrix); + void updateClipRegion_dev(const QRegion ®ion, Qt::ClipOperation op); + + void drawLines(const QLine *lines, int lineCount) Q_DECL_OVERRIDE; + void drawLines(const QLineF *lines, int lineCount) Q_DECL_OVERRIDE; + + void drawRects(const QRect *rects, int rectCount) Q_DECL_OVERRIDE; + void drawRects(const QRectF *rects, int rectCount) Q_DECL_OVERRIDE; + + void drawPoints(const QPoint *points, int pointCount) Q_DECL_OVERRIDE; + void drawPoints(const QPointF *points, int pointCount) Q_DECL_OVERRIDE; + + void drawEllipse(const QRect &r) Q_DECL_OVERRIDE; + void drawEllipse(const QRectF &r) Q_DECL_OVERRIDE; + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) Q_DECL_OVERRIDE; + inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) Q_DECL_OVERRIDE + { QPaintEngine::drawPolygon(points, pointCount, mode); } + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) Q_DECL_OVERRIDE; + void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) Q_DECL_OVERRIDE; + void drawPath(const QPainterPath &path) Q_DECL_OVERRIDE; + void drawTextItem(const QPointF &p, const QTextItem &textItem) Q_DECL_OVERRIDE; + void drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE; + + virtual Drawable handle() const; + inline Type type() const Q_DECL_OVERRIDE { return QPaintEngine::X11; } + + QPainter::RenderHints supportedRenderHints() const; + +protected: + QX11PaintEngine(QX11PaintEnginePrivate &dptr); + +#if QT_CONFIG(fontconfig) + void drawFreetype(const QPointF &p, const QTextItemInt &ti); + bool drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti); +#endif // QT_CONFIG(fontconfig) + + friend class QPixmap; + friend class QFontEngineBox; + friend GC qt_x11_get_pen_gc(QPainter *); + friend GC qt_x11_get_brush_gc(QPainter *); + +private: + Q_DISABLE_COPY(QX11PaintEngine) +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_X11_H diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp new file mode 100644 index 0000000000..f791c90346 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp @@ -0,0 +1,2108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> + +#include <private/qdrawhelper_p.h> +#include <private/qimage_p.h> +#include <private/qimagepixmapcleanuphooks_p.h> + +#include "qxcbnativepainting.h" +#include "qpixmap_x11_p.h" +#include "qcolormap_x11_p.h" +#include "qpaintengine_x11_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_POINTER_SIZE == 8 // 64-bit versions + +Q_ALWAYS_INLINE uint PREMUL(uint x) { + uint a = x >> 24; + quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8; + t &= 0x000000ff00ff00ff; + return (uint(t)) | (uint(t >> 24)) | (a << 24); +} + +#else // 32-bit versions + +Q_ALWAYS_INLINE uint PREMUL(uint x) { + uint a = x >> 24; + uint t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff) * a; + x = (x + ((x >> 8) & 0xff) + 0x80); + x &= 0xff00; + x |= t | (a << 24); + return x; +} +#endif + + + +struct QXImageWrapper +{ + XImage *xi; +}; + +QPixmap qt_toX11Pixmap(const QImage &image) +{ + QPlatformPixmap *data = + new QX11PlatformPixmap(image.depth() == 1 + ? QPlatformPixmap::BitmapType + : QPlatformPixmap::PixmapType); + + data->fromImage(image, Qt::AutoColor); + + return QPixmap(data); +} + +QPixmap qt_toX11Pixmap(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return QPixmap(); + + if (QPixmap(pixmap).data_ptr()->classId() == QPlatformPixmap::X11Class) + return pixmap; + + return qt_toX11Pixmap(pixmap.toImage()); +} + +// For thread-safety: +// image->data does not belong to X11, so we must free it ourselves. + +inline static void qSafeXDestroyImage(XImage *x) +{ + if (x->data) { + free(x->data); + x->data = 0; + } + XDestroyImage(x); +} + +QBitmap QX11PlatformPixmap::mask_to_bitmap(int screen) const +{ + if (!x11_mask) + return QBitmap(); + qt_x11SetDefaultScreen(screen); + QBitmap bm(w, h); + QX11PlatformPixmap *that = qt_x11Pixmap(bm); + const QXcbX11Info *x = that->x11_info(); + GC gc = XCreateGC(x->display(), that->handle(), 0, 0); + XCopyArea(x->display(), x11_mask, that->handle(), gc, 0, 0, + that->width(), that->height(), 0, 0); + XFreeGC(x->display(), gc); + return bm; +} + +void QX11PlatformPixmap::bitmapFromImage(const QImage &image) +{ + w = image.width(); + h = image.height(); + d = 1; + is_null = (w <= 0 || h <= 0); + hd = createBitmapFromImage(image); +#if QT_CONFIG(xrender) + if (X11->use_xrender) + picture = XRenderCreatePicture(xinfo.display(), hd, + XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); +#endif // QT_CONFIG(xrender) +} + +bool QX11PlatformPixmap::canTakeQImageFromXImage(const QXImageWrapper &xiWrapper) const +{ + XImage *xi = xiWrapper.xi; + + if (xi->format != ZPixmap) + return false; + + // ARGB32_Premultiplied + if (picture && depth() == 32) + return true; + + // RGB32 + if (depth() == 24 && xi->bits_per_pixel == 32 && xi->red_mask == 0xff0000 + && xi->green_mask == 0xff00 && xi->blue_mask == 0xff) + return true; + + // RGB16 + if (depth() == 16 && xi->bits_per_pixel == 16 && xi->red_mask == 0xf800 + && xi->green_mask == 0x7e0 && xi->blue_mask == 0x1f) + return true; + + return false; +} + +QImage QX11PlatformPixmap::takeQImageFromXImage(const QXImageWrapper &xiWrapper) const +{ + XImage *xi = xiWrapper.xi; + + QImage::Format format = QImage::Format_ARGB32_Premultiplied; + if (depth() == 24) + format = QImage::Format_RGB32; + else if (depth() == 16) + format = QImage::Format_RGB16; + + QImage image((uchar *)xi->data, xi->width, xi->height, xi->bytes_per_line, format); + image.setDevicePixelRatio(devicePixelRatio()); + // take ownership + image.data_ptr()->own_data = true; + xi->data = 0; + + // we may have to swap the byte order + if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst) + || (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst)) + { + for (int i=0; i < image.height(); i++) { + if (depth() == 16) { + ushort *p = (ushort*)image.scanLine(i); + ushort *end = p + image.width(); + while (p < end) { + *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff); + p++; + } + } else { + uint *p = (uint*)image.scanLine(i); + uint *end = p + image.width(); + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } + } + } + + // fix-up alpha channel + if (format == QImage::Format_RGB32) { + QRgb *p = (QRgb *)image.bits(); + for (int y = 0; y < xi->height; ++y) { + for (int x = 0; x < xi->width; ++x) + p[x] |= 0xff000000; + p += xi->bytes_per_line / 4; + } + } + + XDestroyImage(xi); + return image; +} + +XID QX11PlatformPixmap::bitmap_to_mask(const QBitmap &bitmap, int screen) +{ + if (bitmap.isNull()) + return 0; + QBitmap bm = bitmap; + qt_x11SetScreen(bm, screen); + + QX11PlatformPixmap *that = qt_x11Pixmap(bm); + const QXcbX11Info *x = that->x11_info(); + Pixmap mask = XCreatePixmap(x->display(), RootWindow(x->display(), screen), + that->width(), that->height(), 1); + GC gc = XCreateGC(x->display(), mask, 0, 0); + XCopyArea(x->display(), that->handle(), mask, gc, 0, 0, + that->width(), that->height(), 0, 0); + XFreeGC(x->display(), gc); + return mask; +} + +Drawable qt_x11Handle(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return XNone; + + if (pixmap.handle()->classId() != QPlatformPixmap::X11Class) + return XNone; + + return static_cast<const QX11PlatformPixmap *>(pixmap.handle())->handle(); +} + + +/***************************************************************************** + Internal functions + *****************************************************************************/ + +//extern const uchar *qt_get_bitflip_array(); // defined in qimage.cpp + +// Returns position of highest bit set or -1 if none +static int highest_bit(uint v) +{ + int i; + uint b = (uint)1 << 31; + for (i=31; ((b & v) == 0) && i>=0; i--) + b >>= 1; + return i; +} + +// Counts the number of bits set in 'v' +static uint n_bits(uint v) +{ + int i = 0; + while (v) { + v = v & (v - 1); + i++; + } + return i; +} + +static uint *red_scale_table = 0; +static uint *green_scale_table = 0; +static uint *blue_scale_table = 0; + +static void cleanup_scale_tables() +{ + delete[] red_scale_table; + delete[] green_scale_table; + delete[] blue_scale_table; +} + +/* + Could do smart bitshifting, but the "obvious" algorithm only works for + nBits >= 4. This is more robust. +*/ +static void build_scale_table(uint **table, uint nBits) +{ + if (nBits > 7) { + qWarning("build_scale_table: internal error, nBits = %i", nBits); + return; + } + if (!*table) { + static bool firstTable = true; + if (firstTable) { + qAddPostRoutine(cleanup_scale_tables); + firstTable = false; + } + *table = new uint[256]; + } + int maxVal = (1 << nBits) - 1; + int valShift = 8 - nBits; + int i; + for (i = 0 ; i < maxVal + 1 ; i++) + (*table)[i << valShift] = i*255/maxVal; +} + +static int defaultScreen = -1; + +int qt_x11SetDefaultScreen(int screen) +{ + int old = defaultScreen; + defaultScreen = screen; + return old; +} + +void qt_x11SetScreen(QPixmap &pixmap, int screen) +{ + if (pixmap.paintingActive()) { + qWarning("qt_x11SetScreen(): Cannot change screens during painting"); + return; + } + + if (pixmap.isNull()) + return; + + if (pixmap.handle()->classId() != QPlatformPixmap::X11Class) + return; + + if (screen < 0) + screen = QXcbX11Info::appScreen(); + + QX11PlatformPixmap *pm = static_cast<QX11PlatformPixmap *>(pixmap.handle()); + if (screen == pm->xinfo.screen()) + return; // nothing to do + + if (pixmap.isNull()) { + pm->xinfo = QXcbX11Info::fromScreen(screen); + return; + } + +#if 0 + qDebug("qt_x11SetScreen for %p from %d to %d. Size is %d/%d", pm, pm->xinfo.screen(), screen, pm->width(), pm->height()); +#endif + + qt_x11SetDefaultScreen(screen); + pixmap = qt_toX11Pixmap(pixmap.toImage()); +} + +/***************************************************************************** + QPixmap member functions + *****************************************************************************/ + +QBasicAtomicInt qt_pixmap_serial = Q_BASIC_ATOMIC_INITIALIZER(0); +int Q_GUI_EXPORT qt_x11_preferred_pixmap_depth = 0; + +QX11PlatformPixmap::QX11PlatformPixmap(PixelType pixelType) + : QPlatformPixmap(pixelType, X11Class), hd(0), + flags(Uninitialized), x11_mask(0), picture(0), mask_picture(0), hd2(0), + dpr(1.0), pengine(0) +{} + +QX11PlatformPixmap::~QX11PlatformPixmap() +{ + // Cleanup hooks have to be called before the handles are freed + if (is_cached) { + QImagePixmapCleanupHooks::executePlatformPixmapDestructionHooks(this); + is_cached = false; + } + + release(); +} + +QPlatformPixmap *QX11PlatformPixmap::createCompatiblePlatformPixmap() const +{ + QX11PlatformPixmap *p = new QX11PlatformPixmap(pixelType()); + p->setDevicePixelRatio(devicePixelRatio()); + return p; +} + +void QX11PlatformPixmap::resize(int width, int height) +{ + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + w = width; + h = height; + is_null = (w <= 0 || h <= 0); + + if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { + xinfo = QXcbX11Info::fromScreen(defaultScreen); + } + + int dd = xinfo.depth(); + + if (qt_x11_preferred_pixmap_depth) + dd = qt_x11_preferred_pixmap_depth; + + bool make_null = w <= 0 || h <= 0; // create null pixmap + d = (pixelType() == BitmapType ? 1 : dd); + if (make_null || d == 0) { + w = 0; + h = 0; + is_null = true; + hd = 0; + picture = 0; + d = 0; + if (!make_null) + qWarning("QPixmap: Invalid pixmap parameters"); + return; + } + hd = XCreatePixmap(xinfo.display(), + RootWindow(xinfo.display(), xinfo.screen()), + w, h, d); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = d == 1 + ? XRenderFindStandardFormat(xinfo.display(), PictStandardA1) + : XRenderFindVisualFormat(xinfo.display(), (Visual *) xinfo.visual()); + picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0); + } +#endif // QT_CONFIG(xrender) +} + +struct QX11AlphaDetector +{ + bool hasAlpha() const { + if (checked) + return has; + // Will implicitly also check format and return quickly for opaque types... + checked = true; + has = image->isNull() ? false : const_cast<QImage *>(image)->data_ptr()->checkForAlphaPixels(); + return has; + } + + bool hasXRenderAndAlpha() const { + if (!X11->use_xrender) + return false; + return hasAlpha(); + } + + QX11AlphaDetector(const QImage *i, Qt::ImageConversionFlags flags) + : image(i), checked(false), has(false) + { + if (flags & Qt::NoOpaqueDetection) { + checked = true; + has = image->hasAlphaChannel(); + } + } + + const QImage *image; + mutable bool checked; + mutable bool has; +}; + +void QX11PlatformPixmap::fromImage(const QImage &img, Qt::ImageConversionFlags flags) +{ + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + w = img.width(); + h = img.height(); + d = img.depth(); + is_null = (w <= 0 || h <= 0); + setDevicePixelRatio(img.devicePixelRatio()); + + if (is_null) { + w = h = 0; + return; + } + + if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { + xinfo = QXcbX11Info::fromScreen(defaultScreen); + } + + if (pixelType() == BitmapType) { + bitmapFromImage(img); + return; + } + + if (uint(w) >= 32768 || uint(h) >= 32768) { + w = h = 0; + is_null = true; + return; + } + + QX11AlphaDetector alphaCheck(&img, flags); + int dd = alphaCheck.hasXRenderAndAlpha() ? 32 : xinfo.depth(); + + if (qt_x11_preferred_pixmap_depth) + dd = qt_x11_preferred_pixmap_depth; + + QImage image = img; + + // must be monochrome + if (dd == 1 || (flags & Qt::ColorMode_Mask) == Qt::MonoOnly) { + if (d != 1) { + // dither + image = image.convertToFormat(QImage::Format_MonoLSB, flags); + d = 1; + } + } else { // can be both + bool conv8 = false; + if (d > 8 && dd <= 8) { // convert to 8 bit + if ((flags & Qt::DitherMode_Mask) == Qt::AutoDither) + flags = (flags & ~Qt::DitherMode_Mask) + | Qt::PreferDither; + conv8 = true; + } else if ((flags & Qt::ColorMode_Mask) == Qt::ColorOnly) { + conv8 = (d == 1); // native depth wanted + } else if (d == 1) { + if (image.colorCount() == 2) { + QRgb c0 = image.color(0); // Auto: convert to best + QRgb c1 = image.color(1); + conv8 = qMin(c0,c1) != qRgb(0,0,0) || qMax(c0,c1) != qRgb(255,255,255); + } else { + // eg. 1-color monochrome images (they do exist). + conv8 = true; + } + } + if (conv8) { + image = image.convertToFormat(QImage::Format_Indexed8, flags); + d = 8; + } + } + + if (d == 1 || image.format() > QImage::Format_ARGB32_Premultiplied) { + QImage::Format fmt = QImage::Format_RGB32; + if (alphaCheck.hasXRenderAndAlpha() && d > 1) + fmt = QImage::Format_ARGB32_Premultiplied; + image = image.convertToFormat(fmt, flags); + fromImage(image, Qt::AutoColor); + return; + } + + Display *dpy = xinfo.display(); + Visual *visual = (Visual *)xinfo.visual(); + XImage *xi = 0; + bool trucol = (visual->c_class >= TrueColor); + size_t nbytes = image.sizeInBytes(); + uchar *newbits= 0; + +#if QT_CONFIG(xrender) + if (alphaCheck.hasXRenderAndAlpha()) { + const QImage &cimage = image; + + d = 32; + + if (QXcbX11Info::appDepth() != d) { + xinfo.setDepth(d); + } + + hd = XCreatePixmap(dpy, RootWindow(dpy, xinfo.screen()), w, h, d); + picture = XRenderCreatePicture(dpy, hd, + XRenderFindStandardFormat(dpy, PictStandardARGB32), 0, 0); + + xi = XCreateImage(dpy, visual, d, ZPixmap, 0, 0, w, h, 32, 0); + Q_CHECK_PTR(xi); + newbits = (uchar *)malloc(xi->bytes_per_line*h); + Q_CHECK_PTR(newbits); + xi->data = (char *)newbits; + + switch (cimage.format()) { + case QImage::Format_Indexed8: { + QVector<QRgb> colorTable = cimage.colorTable(); + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const uchar *p = cimage.scanLine(y); + for (int x = 0; x < w; ++x) { + const QRgb rgb = colorTable[p[x]]; + const int a = qAlpha(rgb); + if (a == 0xff) + *xidata = rgb; + else + // RENDER expects premultiplied alpha + *xidata = qRgba(qt_div_255(qRed(rgb) * a), + qt_div_255(qGreen(rgb) * a), + qt_div_255(qBlue(rgb) * a), + a); + ++xidata; + } + } + } + break; + case QImage::Format_RGB32: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + for (int x = 0; x < w; ++x) + *xidata++ = p[x] | 0xff000000; + } + } + break; + case QImage::Format_ARGB32: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + for (int x = 0; x < w; ++x) { + const QRgb rgb = p[x]; + const int a = qAlpha(rgb); + if (a == 0xff) + *xidata = rgb; + else + // RENDER expects premultiplied alpha + *xidata = qRgba(qt_div_255(qRed(rgb) * a), + qt_div_255(qGreen(rgb) * a), + qt_div_255(qBlue(rgb) * a), + a); + ++xidata; + } + } + + } + break; + case QImage::Format_ARGB32_Premultiplied: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + memcpy(xidata, p, w*sizeof(QRgb)); + xidata += w; + } + } + break; + default: + Q_ASSERT(false); + } + + if ((xi->byte_order == MSBFirst) != (QSysInfo::ByteOrder == QSysInfo::BigEndian)) { + uint *xidata = (uint *)xi->data; + uint *xiend = xidata + w*h; + while (xidata < xiend) { + *xidata = (*xidata >> 24) + | ((*xidata >> 8) & 0xff00) + | ((*xidata << 8) & 0xff0000) + | (*xidata << 24); + ++xidata; + } + } + + GC gc = XCreateGC(dpy, hd, 0, 0); + XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); + XFreeGC(dpy, gc); + + qSafeXDestroyImage(xi); + + return; + } +#endif // QT_CONFIG(xrender) + + if (trucol) { // truecolor display + if (image.format() == QImage::Format_ARGB32_Premultiplied) + image = image.convertToFormat(QImage::Format_ARGB32); + + const QImage &cimage = image; + QRgb pix[256]; // pixel translation table + const bool d8 = (d == 8); + const uint red_mask = (uint)visual->red_mask; + const uint green_mask = (uint)visual->green_mask; + const uint blue_mask = (uint)visual->blue_mask; + const int red_shift = highest_bit(red_mask) - 7; + const int green_shift = highest_bit(green_mask) - 7; + const int blue_shift = highest_bit(blue_mask) - 7; + const uint rbits = highest_bit(red_mask) - lowest_bit(red_mask) + 1; + const uint gbits = highest_bit(green_mask) - lowest_bit(green_mask) + 1; + const uint bbits = highest_bit(blue_mask) - lowest_bit(blue_mask) + 1; + + if (d8) { // setup pixel translation + QVector<QRgb> ctable = cimage.colorTable(); + for (int i=0; i < cimage.colorCount(); i++) { + int r = qRed (ctable[i]); + int g = qGreen(ctable[i]); + int b = qBlue (ctable[i]); + r = red_shift > 0 ? r << red_shift : r >> -red_shift; + g = green_shift > 0 ? g << green_shift : g >> -green_shift; + b = blue_shift > 0 ? b << blue_shift : b >> -blue_shift; + pix[i] = (b & blue_mask) | (g & green_mask) | (r & red_mask) + | ~(blue_mask | green_mask | red_mask); + } + } + + xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); + Q_CHECK_PTR(xi); + newbits = (uchar *)malloc(xi->bytes_per_line*h); + Q_CHECK_PTR(newbits); + if (!newbits) // no memory + return; + int bppc = xi->bits_per_pixel; + + bool contig_bits = n_bits(red_mask) == rbits && + n_bits(green_mask) == gbits && + n_bits(blue_mask) == bbits; + bool dither_tc = + // Want it? + (flags & Qt::Dither_Mask) != Qt::ThresholdDither && + (flags & Qt::DitherMode_Mask) != Qt::AvoidDither && + // Need it? + bppc < 24 && !d8 && + // Can do it? (Contiguous bits?) + contig_bits; + + static bool init=false; + static int D[16][16]; + if (dither_tc && !init) { + // I also contributed this code to XV - WWA. + /* + The dither matrix, D, is obtained with this formula: + + D2 = [0 2] + [3 1] + + + D2*n = [4*Dn 4*Dn+2*Un] + [4*Dn+3*Un 4*Dn+1*Un] + */ + int n,i,j; + init=1; + + /* Set D2 */ + D[0][0]=0; + D[1][0]=2; + D[0][1]=3; + D[1][1]=1; + + /* Expand using recursive definition given above */ + for (n=2; n<16; n*=2) { + for (i=0; i<n; i++) { + for (j=0; j<n; j++) { + D[i][j]*=4; + D[i+n][j]=D[i][j]+2; + D[i][j+n]=D[i][j]+3; + D[i+n][j+n]=D[i][j]+1; + } + } + } + init=true; + } + + enum { BPP8, + BPP16_565, BPP16_555, + BPP16_MSB, BPP16_LSB, + BPP24_888, + BPP24_MSB, BPP24_LSB, + BPP32_8888, + BPP32_MSB, BPP32_LSB + } mode = BPP8; + + bool same_msb_lsb = (xi->byte_order == MSBFirst) == (QSysInfo::ByteOrder == QSysInfo::BigEndian); + + if (bppc == 8) // 8 bit + mode = BPP8; + else if (bppc == 16) { // 16 bit MSB/LSB + if (red_shift == 8 && green_shift == 3 && blue_shift == -3 && !d8 && same_msb_lsb) + mode = BPP16_565; + else if (red_shift == 7 && green_shift == 2 && blue_shift == -3 && !d8 && same_msb_lsb) + mode = BPP16_555; + else + mode = (xi->byte_order == LSBFirst) ? BPP16_LSB : BPP16_MSB; + } else if (bppc == 24) { // 24 bit MSB/LSB + if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) + mode = BPP24_888; + else + mode = (xi->byte_order == LSBFirst) ? BPP24_LSB : BPP24_MSB; + } else if (bppc == 32) { // 32 bit MSB/LSB + if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) + mode = BPP32_8888; + else + mode = (xi->byte_order == LSBFirst) ? BPP32_LSB : BPP32_MSB; + } else + qFatal("Logic error 3"); + +#define GET_PIXEL \ + uint pixel; \ + if (d8) pixel = pix[*src++]; \ + else { \ + int r = qRed (*p); \ + int g = qGreen(*p); \ + int b = qBlue (*p++); \ + r = red_shift > 0 \ + ? r << red_shift : r >> -red_shift; \ + g = green_shift > 0 \ + ? g << green_shift : g >> -green_shift; \ + b = blue_shift > 0 \ + ? b << blue_shift : b >> -blue_shift; \ + pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask) \ + | ~(blue_mask | green_mask | red_mask); \ + } + +#define GET_PIXEL_DITHER_TC \ + int r = qRed (*p); \ + int g = qGreen(*p); \ + int b = qBlue (*p++); \ + const int thres = D[x%16][y%16]; \ + if (r <= (255-(1<<(8-rbits))) && ((r<<rbits) & 255) \ + > thres) \ + r += (1<<(8-rbits)); \ + if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \ + > thres) \ + g += (1<<(8-gbits)); \ + if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \ + > thres) \ + b += (1<<(8-bbits)); \ + r = red_shift > 0 \ + ? r << red_shift : r >> -red_shift; \ + g = green_shift > 0 \ + ? g << green_shift : g >> -green_shift; \ + b = blue_shift > 0 \ + ? b << blue_shift : b >> -blue_shift; \ + uint pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask); + +// again, optimized case +// can't be optimized that much :( +#define GET_PIXEL_DITHER_TC_OPT(red_shift,green_shift,blue_shift,red_mask,green_mask,blue_mask, \ + rbits,gbits,bbits) \ + const int thres = D[x%16][y%16]; \ + int r = qRed (*p); \ + if (r <= (255-(1<<(8-rbits))) && ((r<<rbits) & 255) \ + > thres) \ + r += (1<<(8-rbits)); \ + int g = qGreen(*p); \ + if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \ + > thres) \ + g += (1<<(8-gbits)); \ + int b = qBlue (*p++); \ + if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \ + > thres) \ + b += (1<<(8-bbits)); \ + uint pixel = ((r red_shift) & red_mask) \ + | ((g green_shift) & green_mask) \ + | ((b blue_shift) & blue_mask); + +#define CYCLE(body) \ + for (int y=0; y<h; y++) { \ + const uchar* src = cimage.scanLine(y); \ + uchar* dst = newbits + xi->bytes_per_line*y; \ + const QRgb* p = (const QRgb *)src; \ + body \ + } + + if (dither_tc) { + switch (mode) { + case BPP16_565: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC_OPT(<<8,<<3,>>3,0xf800,0x7e0,0x1f,5,6,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC_OPT(<<7,<<2,>>3,0x7c00,0x3e0,0x1f,5,5,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC + *dst++ = (pixel >> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC + *dst++ = pixel; + *dst++ = pixel >> 8; + } + ) + break; + default: + qFatal("Logic error"); + } + } else { + switch (mode) { + case BPP8: // 8 bit + CYCLE( + Q_UNUSED(p); + for (int x=0; x<w; x++) + *dst++ = pix[*src++]; + ) + break; + case BPP16_565: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x = 0; x < w; x++) { + *dst16++ = ((*p >> 8) & 0xf800) + | ((*p >> 5) & 0x7e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x<w; x++) { + *dst16++ = ((*p >> 9) & 0x7c00) + | ((*p >> 6) & 0x3e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = (pixel >> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel; + *dst++ = pixel >> 8; + } + ) + break; + case BPP24_888: + CYCLE( + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { + for (int x=0; x<w; x++) { + *dst++ = qRed (*p); + *dst++ = qGreen(*p); + *dst++ = qBlue (*p++); + } + } else { + for (int x=0; x<w; x++) { + *dst++ = qBlue (*p); + *dst++ = qGreen(*p); + *dst++ = qRed (*p++); + } + } + ) + break; + case BPP24_MSB: // 24 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel >> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP24_LSB: // 24 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel; + *dst++ = pixel >> 8; + *dst++ = pixel >> 16; + } + ) + break; + case BPP32_8888: + CYCLE( + memcpy(dst, p, w * 4); + ) + break; + case BPP32_MSB: // 32 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel >> 24; + *dst++ = pixel >> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP32_LSB: // 32 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel; + *dst++ = pixel >> 8; + *dst++ = pixel >> 16; + *dst++ = pixel >> 24; + } + ) + break; + default: + qFatal("Logic error 2"); + } + } + xi->data = (char *)newbits; + } + + if (d == 8 && !trucol) { // 8 bit pixmap + int pop[256]; // pixel popularity + + if (image.colorCount() == 0) + image.setColorCount(1); + + const QImage &cimage = image; + memset(pop, 0, sizeof(int)*256); // reset popularity array + for (int i = 0; i < h; i++) { // for each scanline... + const uchar* p = cimage.scanLine(i); + const uchar *end = p + w; + while (p < end) // compute popularity + pop[*p++]++; + } + + newbits = (uchar *)malloc(nbytes); // copy image into newbits + Q_CHECK_PTR(newbits); + if (!newbits) // no memory + return; + uchar* p = newbits; + memcpy(p, cimage.bits(), nbytes); // copy image data into newbits + + /* + * The code below picks the most important colors. It is based on the + * diversity algorithm, implemented in XV 3.10. XV is (C) by John Bradley. + */ + + struct PIX { // pixel sort element + uchar r,g,b,n; // color + pad + int use; // popularity + int index; // index in colormap + int mindist; + }; + int ncols = 0; + for (int i=0; i< cimage.colorCount(); i++) { // compute number of colors + if (pop[i] > 0) + ncols++; + } + for (int i = cimage.colorCount(); i < 256; i++) // ignore out-of-range pixels + pop[i] = 0; + + // works since we make sure above to have at least + // one color in the image + if (ncols == 0) + ncols = 1; + + PIX pixarr[256]; // pixel array + PIX pixarr_sorted[256]; // pixel array (sorted) + memset(pixarr, 0, ncols*sizeof(PIX)); + PIX *px = &pixarr[0]; + int maxpop = 0; + int maxpix = 0; + uint j = 0; + QVector<QRgb> ctable = cimage.colorTable(); + for (int i = 0; i < 256; i++) { // init pixel array + if (pop[i] > 0) { + px->r = qRed (ctable[i]); + px->g = qGreen(ctable[i]); + px->b = qBlue (ctable[i]); + px->n = 0; + px->use = pop[i]; + if (pop[i] > maxpop) { // select most popular entry + maxpop = pop[i]; + maxpix = j; + } + px->index = i; + px->mindist = 1000000; + px++; + j++; + } + } + pixarr_sorted[0] = pixarr[maxpix]; + pixarr[maxpix].use = 0; + + for (int i = 1; i < ncols; i++) { // sort pixels + int minpix = -1, mindist = -1; + px = &pixarr_sorted[i-1]; + int r = px->r; + int g = px->g; + int b = px->b; + int dist; + if ((i & 1) || i<10) { // sort on max distance + for (int j=0; j<ncols; j++) { + px = &pixarr[j]; + if (px->use) { + dist = (px->r - r)*(px->r - r) + + (px->g - g)*(px->g - g) + + (px->b - b)*(px->b - b); + if (px->mindist > dist) + px->mindist = dist; + if (px->mindist > mindist) { + mindist = px->mindist; + minpix = j; + } + } + } + } else { // sort on max popularity + for (int j=0; j<ncols; j++) { + px = &pixarr[j]; + if (px->use) { + dist = (px->r - r)*(px->r - r) + + (px->g - g)*(px->g - g) + + (px->b - b)*(px->b - b); + if (px->mindist > dist) + px->mindist = dist; + if (px->use > mindist) { + mindist = px->use; + minpix = j; + } + } + } + } + pixarr_sorted[i] = pixarr[minpix]; + pixarr[minpix].use = 0; + } + + QXcbColormap cmap = QXcbColormap::instance(xinfo.screen()); + uint pix[256]; // pixel translation table + px = &pixarr_sorted[0]; + for (int i = 0; i < ncols; i++) { // allocate colors + QColor c(px->r, px->g, px->b); + pix[px->index] = cmap.pixel(c); + px++; + } + + p = newbits; + for (size_t i = 0; i < nbytes; i++) { // translate pixels + *p = pix[*p]; + p++; + } + } + + if (!xi) { // X image not created + xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); + if (xi->bits_per_pixel == 16) { // convert 8 bpp ==> 16 bpp + ushort *p2; + int p2inc = xi->bytes_per_line/sizeof(ushort); + ushort *newerbits = (ushort *)malloc(xi->bytes_per_line * h); + Q_CHECK_PTR(newerbits); + if (!newerbits) // no memory + return; + uchar* p = newbits; + for (int y = 0; y < h; y++) { // OOPS: Do right byte order!! + p2 = newerbits + p2inc*y; + for (int x = 0; x < w; x++) + *p2++ = *p++; + } + free(newbits); + newbits = (uchar *)newerbits; + } else if (xi->bits_per_pixel != 8) { + qWarning("QPixmap::fromImage: Display not supported " + "(bpp=%d)", xi->bits_per_pixel); + } + xi->data = (char *)newbits; + } + + hd = XCreatePixmap(dpy, + RootWindow(dpy, xinfo.screen()), + w, h, dd); + + GC gc = XCreateGC(dpy, hd, 0, 0); + XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); + XFreeGC(dpy, gc); + + qSafeXDestroyImage(xi); + d = dd; + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = d == 1 + ? XRenderFindStandardFormat(dpy, PictStandardA1) + : XRenderFindVisualFormat(dpy, (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(dpy, hd, format, 0, 0); + } +#endif + + if (alphaCheck.hasAlpha()) { + QBitmap m = QBitmap::fromImage(image.createAlphaMask(flags)); + setMask(m); + } +} + +void QX11PlatformPixmap::copy(const QPlatformPixmap *data, const QRect &rect) +{ + if (data->pixelType() == BitmapType) { + fromImage(data->toImage().copy(rect), Qt::AutoColor); + return; + } + + const QX11PlatformPixmap *x11Data = static_cast<const QX11PlatformPixmap*>(data); + + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + flags &= ~Uninitialized; + xinfo = x11Data->xinfo; + d = x11Data->d; + w = rect.width(); + h = rect.height(); + is_null = (w <= 0 || h <= 0); + hd = XCreatePixmap(xinfo.display(), + RootWindow(xinfo.display(), x11Data->xinfo.screen()), + w, h, d); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = d == 32 + ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32) + : XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0); + } +#endif // QT_CONFIG(xrender) + if (x11Data->x11_mask) { + x11_mask = XCreatePixmap(xinfo.display(), hd, w, h, 1); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask, + XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); + XRenderPictureAttributes attrs; + attrs.alpha_map = x11Data->mask_picture; + XRenderChangePicture(xinfo.display(), x11Data->picture, CPAlphaMap, &attrs); + } +#endif + } + +#if QT_CONFIG(xrender) + if (x11Data->picture && x11Data->d == 32) { + XRenderComposite(xinfo.display(), PictOpSrc, + x11Data->picture, 0, picture, + rect.x(), rect.y(), 0, 0, 0, 0, w, h); + } else +#endif + { + GC gc = XCreateGC(xinfo.display(), hd, 0, 0); + XCopyArea(xinfo.display(), x11Data->hd, hd, gc, + rect.x(), rect.y(), w, h, 0, 0); + if (x11Data->x11_mask) { + GC monogc = XCreateGC(xinfo.display(), x11_mask, 0, 0); + XCopyArea(xinfo.display(), x11Data->x11_mask, x11_mask, monogc, + rect.x(), rect.y(), w, h, 0, 0); + XFreeGC(xinfo.display(), monogc); + } + XFreeGC(xinfo.display(), gc); + } +} + +bool QX11PlatformPixmap::scroll(int dx, int dy, const QRect &rect) +{ + GC gc = XCreateGC(xinfo.display(), hd, 0, 0); + XCopyArea(xinfo.display(), hd, hd, gc, + rect.left(), rect.top(), rect.width(), rect.height(), + rect.left() + dx, rect.top() + dy); + XFreeGC(xinfo.display(), gc); + return true; +} + +int QX11PlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + switch (metric) { + case QPaintDevice::PdmDevicePixelRatio: + return devicePixelRatio(); + break; + case QPaintDevice::PdmDevicePixelRatioScaled: + return devicePixelRatio() * QPaintDevice::devicePixelRatioFScale(); + break; + case QPaintDevice::PdmWidth: + return w; + case QPaintDevice::PdmHeight: + return h; + case QPaintDevice::PdmNumColors: + return 1 << d; + case QPaintDevice::PdmDepth: + return d; + case QPaintDevice::PdmWidthMM: { + const int screen = xinfo.screen(); + const int mm = DisplayWidthMM(xinfo.display(), screen) * w + / DisplayWidth(xinfo.display(), screen); + return mm; + } + case QPaintDevice::PdmHeightMM: { + const int screen = xinfo.screen(); + const int mm = (DisplayHeightMM(xinfo.display(), screen) * h) + / DisplayHeight(xinfo.display(), screen); + return mm; + } + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmPhysicalDpiX: + return QXcbX11Info::appDpiX(xinfo.screen()); + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiY: + return QXcbX11Info::appDpiY(xinfo.screen()); + default: + qWarning("QX11PlatformPixmap::metric(): Invalid metric"); + return 0; + } +} + +void QX11PlatformPixmap::fill(const QColor &fillColor) +{ + if (fillColor.alpha() != 255) { +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + if (!picture || d != 32) + convertToARGB32(/*preserveContents = */false); + + ::Picture src = X11->getSolidFill(xinfo.screen(), fillColor); + XRenderComposite(xinfo.display(), PictOpSrc, src, 0, picture, + 0, 0, width(), height(), + 0, 0, width(), height()); + } else +#endif + { + QImage im(width(), height(), QImage::Format_ARGB32_Premultiplied); + im.fill(PREMUL(fillColor.rgba())); + release(); + fromImage(im, Qt::AutoColor | Qt::OrderedAlphaDither); + } + return; + } + + GC gc = XCreateGC(xinfo.display(), hd, 0, 0); + if (depth() == 1) { + XSetForeground(xinfo.display(), gc, qGray(fillColor.rgb()) > 127 ? 0 : 1); + } else if (X11->use_xrender && d >= 24) { + XSetForeground(xinfo.display(), gc, fillColor.rgba()); + } else { + XSetForeground(xinfo.display(), gc, + QXcbColormap::instance(xinfo.screen()).pixel(fillColor)); + } + XFillRectangle(xinfo.display(), hd, gc, 0, 0, width(), height()); + XFreeGC(xinfo.display(), gc); +} + +QBitmap QX11PlatformPixmap::mask() const +{ + QBitmap mask; +#if QT_CONFIG(xrender) + if (picture && d == 32) { + // #### slow - there must be a better way.. + mask = QBitmap::fromImage(toImage().createAlphaMask()); + } else +#endif + if (d == 1) { + QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this); + mask = QPixmap(that); + } else { + mask = mask_to_bitmap(xinfo.screen()); + } + return mask; +} + +void QX11PlatformPixmap::setMask(const QBitmap &newmask) +{ + if (newmask.isNull()) { // clear mask +#if QT_CONFIG(xrender) + if (picture && d == 32) { + QX11PlatformPixmap newData(pixelType()); + newData.resize(w, h); + newData.fill(Qt::black); + XRenderComposite(xinfo.display(), PictOpOver, + picture, 0, newData.picture, + 0, 0, 0, 0, 0, 0, w, h); + release(); + *this = newData; + // the new QX11PlatformPixmap object isn't referenced yet, so + // ref it + ref.ref(); + + // the below is to make sure the QX11PlatformPixmap destructor + // doesn't delete our newly created render picture + newData.hd = 0; + newData.x11_mask = 0; + newData.picture = 0; + newData.mask_picture = 0; + newData.hd2 = 0; + } else +#endif + if (x11_mask) { +#if QT_CONFIG(xrender) + if (picture) { + XRenderPictureAttributes attrs; + attrs.alpha_map = 0; + XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, + &attrs); + } + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); + mask_picture = 0; +#endif + XFreePixmap(xinfo.display(), x11_mask); + x11_mask = 0; + } + return; + } + +#if QT_CONFIG(xrender) + if (picture && d == 32) { + XRenderComposite(xinfo.display(), PictOpSrc, + picture, qt_x11Pixmap(newmask)->x11PictureHandle(), + picture, 0, 0, 0, 0, 0, 0, w, h); + } else +#endif + if (depth() == 1) { + XGCValues vals; + vals.function = GXand; + GC gc = XCreateGC(xinfo.display(), hd, GCFunction, &vals); + XCopyArea(xinfo.display(), qt_x11Pixmap(newmask)->handle(), hd, gc, 0, 0, + width(), height(), 0, 0); + XFreeGC(xinfo.display(), gc); + } else { + // ##### should or the masks together + if (x11_mask) { + XFreePixmap(xinfo.display(), x11_mask); +#if QT_CONFIG(xrender) + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); +#endif + } + x11_mask = QX11PlatformPixmap::bitmap_to_mask(newmask, xinfo.screen()); +#if QT_CONFIG(xrender) + if (picture) { + mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask, + XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); + XRenderPictureAttributes attrs; + attrs.alpha_map = mask_picture; + XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, &attrs); + } +#endif + } +} + +bool QX11PlatformPixmap::hasAlphaChannel() const +{ + if (picture && d == 32) + return true; + + if (x11_mask && d == 1) + return true; + + return false; +} + +QPixmap QX11PlatformPixmap::transformed(const QTransform &transform, Qt::TransformationMode mode) const +{ + if (mode == Qt::SmoothTransformation || transform.type() >= QTransform::TxProject) { + QImage image = toImage(); + return QPixmap::fromImage(image.transformed(transform, mode)); + } + + uint w = 0; + uint h = 0; // size of target pixmap + uint ws, hs; // size of source pixmap + uchar *dptr; // data in target pixmap + uint dbpl, dbytes; // bytes per line/bytes total + uchar *sptr; // data in original pixmap + int sbpl; // bytes per line in original + int bpp; // bits per pixel + bool depth1 = depth() == 1; + Display *dpy = xinfo.display(); + + ws = width(); + hs = height(); + + QTransform mat(transform.m11(), transform.m12(), transform.m13(), + transform.m21(), transform.m22(), transform.m23(), + 0., 0., 1); + bool complex_xform = false; + qreal scaledWidth; + qreal scaledHeight; + + if (mat.type() <= QTransform::TxScale) { + scaledHeight = qAbs(mat.m22()) * hs + 0.9999; + scaledWidth = qAbs(mat.m11()) * ws + 0.9999; + h = qAbs(int(scaledHeight)); + w = qAbs(int(scaledWidth)); + } else { // rotation or shearing + QPolygonF a(QRectF(0, 0, ws, hs)); + a = mat.map(a); + QRect r = a.boundingRect().toAlignedRect(); + w = r.width(); + h = r.height(); + scaledWidth = w; + scaledHeight = h; + complex_xform = true; + } + mat = QPixmap::trueMatrix(mat, ws, hs); // true matrix + + bool invertible; + mat = mat.inverted(&invertible); // invert matrix + + if (h == 0 || w == 0 || !invertible + || qAbs(scaledWidth) >= 32768 || qAbs(scaledHeight) >= 32768 ) + // error, return null pixmap + return QPixmap(); + + XImage *xi = XGetImage(xinfo.display(), handle(), 0, 0, ws, hs, AllPlanes, + depth1 ? XYPixmap : ZPixmap); + + if (!xi) + return QPixmap(); + + sbpl = xi->bytes_per_line; + sptr = (uchar *)xi->data; + bpp = xi->bits_per_pixel; + + if (depth1) + dbpl = (w+7)/8; + else + dbpl = ((w*bpp+31)/32)*4; + dbytes = dbpl*h; + + dptr = (uchar *)malloc(dbytes); // create buffer for bits + Q_CHECK_PTR(dptr); + if (depth1) // fill with zeros + memset(dptr, 0, dbytes); + else if (bpp == 8) // fill with background color + memset(dptr, WhitePixel(xinfo.display(), xinfo.screen()), dbytes); + else + memset(dptr, 0, dbytes); + + // #define QT_DEBUG_XIMAGE +#if defined(QT_DEBUG_XIMAGE) + qDebug("----IMAGE--INFO--------------"); + qDebug("width............. %d", xi->width); + qDebug("height............ %d", xi->height); + qDebug("xoffset........... %d", xi->xoffset); + qDebug("format............ %d", xi->format); + qDebug("byte order........ %d", xi->byte_order); + qDebug("bitmap unit....... %d", xi->bitmap_unit); + qDebug("bitmap bit order.. %d", xi->bitmap_bit_order); + qDebug("depth............. %d", xi->depth); + qDebug("bytes per line.... %d", xi->bytes_per_line); + qDebug("bits per pixel.... %d", xi->bits_per_pixel); +#endif + + int type; + if (xi->bitmap_bit_order == MSBFirst) + type = QT_XFORM_TYPE_MSBFIRST; + else + type = QT_XFORM_TYPE_LSBFIRST; + int xbpl, p_inc; + if (depth1) { + xbpl = (w+7)/8; + p_inc = dbpl - xbpl; + } else { + xbpl = (w*bpp)/8; + p_inc = dbpl - xbpl; + } + + if (!qt_xForm_helper(mat, xi->xoffset, type, bpp, dptr, xbpl, p_inc, h, sptr, sbpl, ws, hs)){ + qWarning("QPixmap::transform: display not supported (bpp=%d)",bpp); + QPixmap pm; + free(dptr); + return pm; + } + + qSafeXDestroyImage(xi); + + if (depth1) { // mono bitmap + QBitmap bm = QBitmap::fromData(QSize(w, h), dptr, + BitmapBitOrder(xinfo.display()) == MSBFirst + ? QImage::Format_Mono + : QImage::Format_MonoLSB); + free(dptr); + return bm; + } else { // color pixmap + QX11PlatformPixmap *x11Data = new QX11PlatformPixmap(QPlatformPixmap::PixmapType); + QPixmap pm(x11Data); + x11Data->flags &= ~QX11PlatformPixmap::Uninitialized; + x11Data->xinfo = xinfo; + x11Data->d = d; + x11Data->w = w; + x11Data->h = h; + x11Data->is_null = (w <= 0 || h <= 0); + x11Data->hd = XCreatePixmap(xinfo.display(), + RootWindow(xinfo.display(), xinfo.screen()), + w, h, d); + x11Data->setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = x11Data->d == 32 + ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32) + : XRenderFindVisualFormat(xinfo.display(), (Visual *) x11Data->xinfo.visual()); + x11Data->picture = XRenderCreatePicture(xinfo.display(), x11Data->hd, format, 0, 0); + } +#endif // QT_CONFIG(xrender) + + GC gc = XCreateGC(xinfo.display(), x11Data->hd, 0, 0); + xi = XCreateImage(dpy, (Visual*)x11Data->xinfo.visual(), + x11Data->d, + ZPixmap, 0, (char *)dptr, w, h, 32, 0); + XPutImage(dpy, qt_x11Pixmap(pm)->handle(), gc, xi, 0, 0, 0, 0, w, h); + qSafeXDestroyImage(xi); + XFreeGC(xinfo.display(), gc); + + if (x11_mask) { // xform mask, too + pm.setMask(mask_to_bitmap(xinfo.screen()).transformed(transform)); + } else if (d != 32 && complex_xform) { // need a mask! + QBitmap mask(ws, hs); + mask.fill(Qt::color1); + pm.setMask(mask.transformed(transform)); + } + return pm; + } +} + +QImage QX11PlatformPixmap::toImage() const +{ + return toImage(QRect(0, 0, w, h)); +} + +QImage QX11PlatformPixmap::toImage(const QRect &rect) const +{ + Window root_return; + int x_return; + int y_return; + unsigned int width_return; + unsigned int height_return; + unsigned int border_width_return; + unsigned int depth_return; + + XGetGeometry(xinfo.display(), hd, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return); + + QXImageWrapper xiWrapper; + xiWrapper.xi = XGetImage(xinfo.display(), hd, rect.x(), rect.y(), rect.width(), rect.height(), + AllPlanes, (depth() == 1) ? XYPixmap : ZPixmap); + + Q_CHECK_PTR(xiWrapper.xi); + if (!xiWrapper.xi) + return QImage(); + + if (!x11_mask && canTakeQImageFromXImage(xiWrapper)) + return takeQImageFromXImage(xiWrapper); + + QImage image = toImage(xiWrapper, rect); + qSafeXDestroyImage(xiWrapper.xi); + return image; +} + +#if QT_CONFIG(xrender) +static XRenderPictFormat *qt_renderformat_for_depth(const QXcbX11Info &xinfo, int depth) +{ + if (depth == 1) + return XRenderFindStandardFormat(xinfo.display(), PictStandardA1); + else if (depth == 32) + return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32); + else + return XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual()); +} +#endif + +Q_GLOBAL_STATIC(QX11PaintEngine, qt_x11_paintengine) + +QPaintEngine *QX11PlatformPixmap::paintEngine() const +{ + QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this); + + if ((flags & Readonly)/* && share_mode == QPixmap::ImplicitlyShared*/) { + // if someone wants to draw onto us, copy the shared contents + // and turn it into a fully fledged QPixmap + ::Pixmap hd_copy = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()), + w, h, d); +#if QT_CONFIG(xrender) + if (picture && d == 32) { + XRenderPictFormat *format = qt_renderformat_for_depth(xinfo, d); + ::Picture picture_copy = XRenderCreatePicture(xinfo.display(), + hd_copy, format, + 0, 0); + + XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, picture_copy, + 0, 0, 0, 0, 0, 0, w, h); + XRenderFreePicture(xinfo.display(), picture); + that->picture = picture_copy; + } else +#endif + { + GC gc = XCreateGC(xinfo.display(), hd_copy, 0, 0); + XCopyArea(xinfo.display(), hd, hd_copy, gc, 0, 0, w, h, 0, 0); + XFreeGC(xinfo.display(), gc); + } + that->hd = hd_copy; + that->flags &= ~QX11PlatformPixmap::Readonly; + } + + if (qt_x11_paintengine->isActive()) { + if (!that->pengine) + that->pengine = new QX11PaintEngine; + + return that->pengine; + } + + return qt_x11_paintengine(); +} + +qreal QX11PlatformPixmap::devicePixelRatio() const +{ + return dpr; +} + +void QX11PlatformPixmap::setDevicePixelRatio(qreal scaleFactor) +{ + dpr = scaleFactor; +} + +Pixmap QX11PlatformPixmap::x11ConvertToDefaultDepth() +{ +#if QT_CONFIG(xrender) + if (d == xinfo.appDepth() || !X11->use_xrender) + return hd; + if (!hd2) { + hd2 = XCreatePixmap(xinfo.display(), hd, w, h, xinfo.appDepth()); + XRenderPictFormat *format = XRenderFindVisualFormat(xinfo.display(), + (Visual*) xinfo.visual()); + Picture pic = XRenderCreatePicture(xinfo.display(), hd2, format, 0, 0); + XRenderComposite(xinfo.display(), PictOpSrc, picture, + XNone, pic, 0, 0, 0, 0, 0, 0, w, h); + XRenderFreePicture(xinfo.display(), pic); + } + return hd2; +#else + return hd; +#endif +} + +XID QX11PlatformPixmap::createBitmapFromImage(const QImage &image) +{ + QImage img = image.convertToFormat(QImage::Format_MonoLSB); + const QRgb c0 = QColor(Qt::black).rgb(); + const QRgb c1 = QColor(Qt::white).rgb(); + if (img.color(0) == c0 && img.color(1) == c1) { + img.invertPixels(); + img.setColor(0, c1); + img.setColor(1, c0); + } + + char *bits; + uchar *tmp_bits; + int w = img.width(); + int h = img.height(); + int bpl = (w + 7) / 8; + int ibpl = img.bytesPerLine(); + if (bpl != ibpl) { + tmp_bits = new uchar[bpl*h]; + bits = (char *)tmp_bits; + uchar *p, *b; + int y; + b = tmp_bits; + p = img.scanLine(0); + for (y = 0; y < h; y++) { + memcpy(b, p, bpl); + b += bpl; + p += ibpl; + } + } else { + bits = (char *)img.bits(); + tmp_bits = 0; + } + XID hd = XCreateBitmapFromData(QXcbX11Info::display(), + QXcbX11Info::appRootWindow(), + bits, w, h); + if (tmp_bits) // Avoid purify complaint + delete [] tmp_bits; + return hd; +} + +#if QT_CONFIG(xrender) +void QX11PlatformPixmap::convertToARGB32(bool preserveContents) +{ + if (!X11->use_xrender) + return; + + // Q_ASSERT(count == 1); + if ((flags & Readonly)/* && share_mode == QPixmap::ExplicitlyShared*/) + return; + + Pixmap pm = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()), + w, h, 32); + Picture p = XRenderCreatePicture(xinfo.display(), pm, + XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32), 0, 0); + if (picture) { + if (preserveContents) + XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, p, 0, 0, 0, 0, 0, 0, w, h); + if (!(flags & Readonly)) + XRenderFreePicture(xinfo.display(), picture); + } + if (hd && !(flags & Readonly)) + XFreePixmap(xinfo.display(), hd); + if (x11_mask) { + XFreePixmap(xinfo.display(), x11_mask); + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); + x11_mask = 0; + mask_picture = 0; + } + hd = pm; + picture = p; + + d = 32; + xinfo.setDepth(32); + + XVisualInfo visinfo; + if (XMatchVisualInfo(xinfo.display(), xinfo.screen(), 32, TrueColor, &visinfo)) + xinfo.setVisual(visinfo.visual); +} +#endif + +void QX11PlatformPixmap::release() +{ + delete pengine; + pengine = 0; + + if (/*!X11*/ QCoreApplication::closingDown()) { + // At this point, the X server will already have freed our resources, + // so there is nothing to do. + return; + } + + if (x11_mask) { +#if QT_CONFIG(xrender) + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); + mask_picture = 0; +#endif + XFreePixmap(xinfo.display(), x11_mask); + x11_mask = 0; + } + + if (hd) { +#if QT_CONFIG(xrender) + if (picture) { + XRenderFreePicture(xinfo.display(), picture); + picture = 0; + } +#endif // QT_CONFIG(xrender) + + if (hd2) { + XFreePixmap(xinfo.display(), hd2); + hd2 = 0; + } + if (!(flags & Readonly)) + XFreePixmap(xinfo.display(), hd); + hd = 0; + } +} + +QImage QX11PlatformPixmap::toImage(const QXImageWrapper &xiWrapper, const QRect &rect) const +{ + XImage *xi = xiWrapper.xi; + + int d = depth(); + Visual *visual = (Visual *)xinfo.visual(); + bool trucol = (visual->c_class >= TrueColor) && d > 1; + + QImage::Format format = QImage::Format_Mono; + if (d > 1 && d <= 8) { + d = 8; + format = QImage::Format_Indexed8; + } + // we could run into the situation where d == 8 AND trucol is true, which can + // cause problems when converting to and from images. in this case, always treat + // the depth as 32... + if (d > 8 || trucol) { + d = 32; + format = QImage::Format_RGB32; + } + + if (d == 1 && xi->bitmap_bit_order == LSBFirst) + format = QImage::Format_MonoLSB; + if (x11_mask && format == QImage::Format_RGB32) + format = QImage::Format_ARGB32; + + QImage image(xi->width, xi->height, format); + image.setDevicePixelRatio(devicePixelRatio()); + if (image.isNull()) // could not create image + return image; + + QImage alpha; + if (x11_mask) { + if (rect.contains(QRect(0, 0, w, h))) + alpha = mask().toImage(); + else + alpha = mask().toImage().copy(rect); + } + bool ale = alpha.format() == QImage::Format_MonoLSB; + + if (trucol) { // truecolor + const uint red_mask = (uint)visual->red_mask; + const uint green_mask = (uint)visual->green_mask; + const uint blue_mask = (uint)visual->blue_mask; + const int red_shift = highest_bit(red_mask) - 7; + const int green_shift = highest_bit(green_mask) - 7; + const int blue_shift = highest_bit(blue_mask) - 7; + + const uint red_bits = n_bits(red_mask); + const uint green_bits = n_bits(green_mask); + const uint blue_bits = n_bits(blue_mask); + + static uint red_table_bits = 0; + static uint green_table_bits = 0; + static uint blue_table_bits = 0; + + if (red_bits < 8 && red_table_bits != red_bits) { + build_scale_table(&red_scale_table, red_bits); + red_table_bits = red_bits; + } + if (blue_bits < 8 && blue_table_bits != blue_bits) { + build_scale_table(&blue_scale_table, blue_bits); + blue_table_bits = blue_bits; + } + if (green_bits < 8 && green_table_bits != green_bits) { + build_scale_table(&green_scale_table, green_bits); + green_table_bits = green_bits; + } + + int r, g, b; + + QRgb *dst; + uchar *src; + uint pixel; + int bppc = xi->bits_per_pixel; + + if (bppc > 8 && xi->byte_order == LSBFirst) + bppc++; + + for (int y = 0; y < xi->height; ++y) { + uchar* asrc = x11_mask ? alpha.scanLine(y) : 0; + dst = (QRgb *)image.scanLine(y); + src = (uchar *)xi->data + xi->bytes_per_line*y; + for (int x = 0; x < xi->width; x++) { + switch (bppc) { + case 8: + pixel = *src++; + break; + case 16: // 16 bit MSB + pixel = src[1] | (uint)src[0] << 8; + src += 2; + break; + case 17: // 16 bit LSB + pixel = src[0] | (uint)src[1] << 8; + src += 2; + break; + case 24: // 24 bit MSB + pixel = src[2] | (uint)src[1] << 8 | (uint)src[0] << 16; + src += 3; + break; + case 25: // 24 bit LSB + pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16; + src += 3; + break; + case 32: // 32 bit MSB + pixel = src[3] | (uint)src[2] << 8 | (uint)src[1] << 16 | (uint)src[0] << 24; + src += 4; + break; + case 33: // 32 bit LSB + pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16 | (uint)src[3] << 24; + src += 4; + break; + default: // should not really happen + x = xi->width; // leave loop + y = xi->height; + pixel = 0; // eliminate compiler warning + qWarning("QPixmap::convertToImage: Invalid depth %d", bppc); + } + if (red_shift > 0) + r = (pixel & red_mask) >> red_shift; + else + r = (pixel & red_mask) << -red_shift; + if (green_shift > 0) + g = (pixel & green_mask) >> green_shift; + else + g = (pixel & green_mask) << -green_shift; + if (blue_shift > 0) + b = (pixel & blue_mask) >> blue_shift; + else + b = (pixel & blue_mask) << -blue_shift; + + if (red_bits < 8) + r = red_scale_table[r]; + if (green_bits < 8) + g = green_scale_table[g]; + if (blue_bits < 8) + b = blue_scale_table[b]; + + if (x11_mask) { + if (ale) { + *dst++ = (asrc[x >> 3] & (1 << (x & 7))) ? qRgba(r, g, b, 0xff) : 0; + } else { + *dst++ = (asrc[x >> 3] & (0x80 >> (x & 7))) ? qRgba(r, g, b, 0xff) : 0; + } + } else { + *dst++ = qRgb(r, g, b); + } + } + } + } else if (xi->bits_per_pixel == d) { // compatible depth + char *xidata = xi->data; // copy each scanline + int bpl = qMin(image.bytesPerLine(),xi->bytes_per_line); + for (int y=0; y<xi->height; y++) { + memcpy(image.scanLine(y), xidata, bpl); + xidata += xi->bytes_per_line; + } + } else { + /* Typically 2 or 4 bits display depth */ + qWarning("QPixmap::convertToImage: Display not supported (bpp=%d)", + xi->bits_per_pixel); + return QImage(); + } + + if (d == 1) { // bitmap + image.setColorCount(2); + image.setColor(0, qRgb(255,255,255)); + image.setColor(1, qRgb(0,0,0)); + } else if (!trucol) { // pixmap with colormap + uchar *p; + uchar *end; + uchar use[256]; // pixel-in-use table + uchar pix[256]; // pixel translation table + int ncols, bpl; + memset(use, 0, 256); + memset(pix, 0, 256); + bpl = image.bytesPerLine(); + + if (x11_mask) { // which pixels are used? + for (int i = 0; i < xi->height; i++) { + uchar* asrc = alpha.scanLine(i); + p = image.scanLine(i); + if (ale) { + for (int x = 0; x < xi->width; x++) { + if (asrc[x >> 3] & (1 << (x & 7))) + use[*p] = 1; + ++p; + } + } else { + for (int x = 0; x < xi->width; x++) { + if (asrc[x >> 3] & (0x80 >> (x & 7))) + use[*p] = 1; + ++p; + } + } + } + } else { + for (int i = 0; i < xi->height; i++) { + p = image.scanLine(i); + end = p + bpl; + while (p < end) + use[*p++] = 1; + } + } + ncols = 0; + for (int i = 0; i < 256; i++) { // build translation table + if (use[i]) + pix[i] = ncols++; + } + for (int i = 0; i < xi->height; i++) { // translate pixels + p = image.scanLine(i); + end = p + bpl; + while (p < end) { + *p = pix[*p]; + p++; + } + } + if (x11_mask) { + int trans; + if (ncols < 256) { + trans = ncols++; + image.setColorCount(ncols); // create color table + image.setColor(trans, 0x00000000); + } else { + image.setColorCount(ncols); // create color table + // oh dear... no spare "transparent" pixel. + // use first pixel in image (as good as any). + trans = image.scanLine(0)[0]; + } + for (int i = 0; i < xi->height; i++) { + uchar* asrc = alpha.scanLine(i); + p = image.scanLine(i); + if (ale) { + for (int x = 0; x < xi->width; x++) { + if (!(asrc[x >> 3] & (1 << (x & 7)))) + *p = trans; + ++p; + } + } else { + for (int x = 0; x < xi->width; x++) { + if (!(asrc[x >> 3] & (1 << (7 -(x & 7))))) + *p = trans; + ++p; + } + } + } + } else { + image.setColorCount(ncols); // create color table + } + QVector<QColor> colors = QXcbColormap::instance(xinfo.screen()).colormap(); + int j = 0; + for (int i=0; i<colors.size(); i++) { // translate pixels + if (use[i]) + image.setColor(j++, 0xff000000 | colors.at(i).rgb()); + } + } + + return image; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h new file mode 100644 index 0000000000..2cbd1fe3d0 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QX11PLATFORMPIXMAP_H +#define QX11PLATFORMPIXMAP_H + +#include <QBitmap> +#include <QPixmap> + +#include <qpa/qplatformpixmap.h> +#include "qxcbnativepainting.h" + +typedef unsigned long XID; +typedef XID Drawable; +typedef XID Picture; +typedef XID Pixmap; + +QT_BEGIN_NAMESPACE + +class QX11PaintEngine; +struct QXImageWrapper; + +class QX11PlatformPixmap : public QPlatformPixmap +{ +public: + QX11PlatformPixmap(PixelType pixelType); + ~QX11PlatformPixmap(); + + QPlatformPixmap *createCompatiblePlatformPixmap() const Q_DECL_OVERRIDE; + void resize(int width, int height) Q_DECL_OVERRIDE; + void fromImage(const QImage &img, Qt::ImageConversionFlags flags) Q_DECL_OVERRIDE; + void copy(const QPlatformPixmap *data, const QRect &rect) Q_DECL_OVERRIDE; + bool scroll(int dx, int dy, const QRect &rect) Q_DECL_OVERRIDE; + int metric(QPaintDevice::PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + void fill(const QColor &fillColor) Q_DECL_OVERRIDE; + QBitmap mask() const Q_DECL_OVERRIDE; + void setMask(const QBitmap &mask) Q_DECL_OVERRIDE; + bool hasAlphaChannel() const Q_DECL_OVERRIDE; + QPixmap transformed(const QTransform &matrix, Qt::TransformationMode mode) const Q_DECL_OVERRIDE; + QImage toImage() const Q_DECL_OVERRIDE; + QImage toImage(const QRect &rect) const Q_DECL_OVERRIDE; + QPaintEngine *paintEngine() const Q_DECL_OVERRIDE; + qreal devicePixelRatio() const Q_DECL_OVERRIDE; + void setDevicePixelRatio(qreal scaleFactor) Q_DECL_OVERRIDE; + + inline Drawable handle() const { return hd; } + inline Picture x11PictureHandle() const { return picture; } + inline const QXcbX11Info *x11_info() const { return &xinfo; } + + Pixmap x11ConvertToDefaultDepth(); + static XID createBitmapFromImage(const QImage &image); + +#if QT_CONFIG(xrender) + void convertToARGB32(bool preserveContents = true); +#endif + +private: + friend class QX11PaintEngine; + friend const QXcbX11Info &qt_x11Info(const QPixmap &pixmap); + friend void qt_x11SetScreen(QPixmap &pixmap, int screen); + + void release(); + QImage toImage(const QXImageWrapper &xi, const QRect &rect) const; + QBitmap mask_to_bitmap(int screen) const; + static Pixmap bitmap_to_mask(const QBitmap &, int screen); + void bitmapFromImage(const QImage &image); + bool canTakeQImageFromXImage(const QXImageWrapper &xi) const; + QImage takeQImageFromXImage(const QXImageWrapper &xi) const; + + Pixmap hd = 0; + + enum Flag { + NoFlags = 0x0, + Uninitialized = 0x1, + Readonly = 0x2, + InvertedWhenBoundToTexture = 0x4, + GlSurfaceCreatedWithAlpha = 0x8 + }; + uint flags; + + QXcbX11Info xinfo; + Pixmap x11_mask; + Picture picture; + Picture mask_picture; + Pixmap hd2; // sorted in the default display depth + //QPixmap::ShareMode share_mode; + qreal dpr; + + QX11PaintEngine *pengine; +}; + +inline QX11PlatformPixmap *qt_x11Pixmap(const QPixmap &pixmap) +{ + return (pixmap.handle() && pixmap.handle()->classId() == QPlatformPixmap::X11Class) + ? static_cast<QX11PlatformPixmap *>(pixmap.handle()) + : Q_NULLPTR; +} + +inline Picture qt_x11PictureHandle(const QPixmap &pixmap) +{ + if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) + return pm->x11PictureHandle(); + + return 0; +} + +inline Pixmap qt_x11PixmapHandle(const QPixmap &pixmap) +{ + if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) + return pm->handle(); + + return 0; +} + +inline const QXcbX11Info &qt_x11Info(const QPixmap &pixmap) +{ + if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) { + return pm->xinfo; + } else { + static QXcbX11Info nullX11Info; + return nullX11Info; + } +} + +int qt_x11SetDefaultScreen(int screen); +void qt_x11SetScreen(QPixmap &pixmap, int screen); + +QT_END_NAMESPACE + +#endif // QX11PLATFORMPIXMAP_H diff --git a/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h new file mode 100644 index 0000000000..a0e5131ea7 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_X11_P_H +#define QT_X11_P_H + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#if QT_CONFIG(xrender) +# include "qtessellator_p.h" +# include <X11/extensions/Xrender.h> +#endif + +#if QT_CONFIG(fontconfig) +#include <fontconfig/fontconfig.h> +#endif + +#if defined(FT_LCD_FILTER_H) +#include FT_LCD_FILTER_H +#endif + +#if defined(FC_LCD_FILTER) + +#ifndef FC_LCD_FILTER_NONE +#define FC_LCD_FILTER_NONE FC_LCD_NONE +#endif + +#ifndef FC_LCD_FILTER_DEFAULT +#define FC_LCD_FILTER_DEFAULT FC_LCD_DEFAULT +#endif + +#ifndef FC_LCD_FILTER_LIGHT +#define FC_LCD_FILTER_LIGHT FC_LCD_LIGHT +#endif + +#ifndef FC_LCD_FILTER_LEGACY +#define FC_LCD_FILTER_LEGACY FC_LCD_LEGACY +#endif + +#endif + +QT_BEGIN_NAMESPACE + +// rename a couple of X defines to get rid of name clashes +// resolve the conflict between X11's FocusIn and QEvent::FocusIn +enum { + XFocusOut = FocusOut, + XFocusIn = FocusIn, + XKeyPress = KeyPress, + XKeyRelease = KeyRelease, + XNone = None, + XRevertToParent = RevertToParent, + XGrayScale = GrayScale, + XCursorShape = CursorShape, +}; +#undef FocusOut +#undef FocusIn +#undef KeyPress +#undef KeyRelease +#undef None +#undef RevertToParent +#undef GrayScale +#undef CursorShape + +#ifdef FontChange +#undef FontChange +#endif + +Q_DECLARE_TYPEINFO(XPoint, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(XRectangle, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(XChar2b, Q_PRIMITIVE_TYPE); +#if QT_CONFIG(xrender) +Q_DECLARE_TYPEINFO(XGlyphElt32, Q_PRIMITIVE_TYPE); +#endif + +struct QX11InfoData; + +enum DesktopEnvironment { + DE_UNKNOWN, + DE_KDE, + DE_GNOME, + DE_CDE, + DE_MEEGO_COMPOSITOR, + DE_4DWM +}; + +struct QXcbX11Data { + Display *display = nullptr; + + // true if Qt is compiled w/ RENDER support and RENDER is supported on the connected Display + bool use_xrender = false; + int xrender_major = 0; + int xrender_version = 0; + + QX11InfoData *screens = nullptr; + Visual **argbVisuals = nullptr; + Colormap *argbColormaps = nullptr; + int screenCount = 0; + int defaultScreen = 0; + + // options + int visual_class = 0; + int visual_id = 0; + int color_count = 0; + bool custom_cmap = false; + + // outside visual/colormap + Visual *visual = nullptr; + Colormap colormap = 0; + +#if QT_CONFIG(xrender) + enum { solid_fill_count = 16 }; + struct SolidFills { + XRenderColor color; + int screen; + Picture picture; + } solid_fills[solid_fill_count]; + enum { pattern_fill_count = 16 }; + struct PatternFills { + XRenderColor color; + XRenderColor bg_color; + int screen; + int style; + bool opaque; + Picture picture; + } pattern_fills[pattern_fill_count]; + Picture getSolidFill(int screen, const QColor &c); + XRenderColor preMultiply(const QColor &c); +#endif + + bool fc_antialias = true; + int fc_hint_style = 0; + + DesktopEnvironment desktopEnvironment = DE_GNOME; +}; + +extern QXcbX11Data *qt_x11Data; +#define X11 qt_x11Data + +struct QX11InfoData { + int screen; + int dpiX; + int dpiY; + int depth; + int cells; + Colormap colormap; + Visual *visual; + bool defaultColormap; + bool defaultVisual; + int subpixel = 0; +}; + +template <class T> +Q_DECL_RELAXED_CONSTEXPR inline int lowest_bit(T v) Q_DECL_NOTHROW +{ + int result = qCountTrailingZeroBits(v); + return ((result >> 3) == sizeof(T)) ? -1 : result; +} + +QT_END_NAMESPACE + +#endif // QT_X11_P_H diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp new file mode 100644 index 0000000000..9acb21f95f --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp @@ -0,0 +1,1494 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtessellator_p.h" + +#include <QRect> +#include <QList> +#include <QDebug> + +#include <qmath.h> +#include <limits.h> +#include <algorithm> + +QT_BEGIN_NAMESPACE + +//#define DEBUG +#ifdef DEBUG +#define QDEBUG qDebug +#else +#define QDEBUG if (1){} else qDebug +#endif + +static const bool emit_clever = true; +static const bool mark_clever = false; + +enum VertexFlags { + LineBeforeStarts = 0x1, + LineBeforeEnds = 0x2, + LineBeforeHorizontal = 0x4, + LineAfterStarts = 0x8, + LineAfterEnds = 0x10, + LineAfterHorizontal = 0x20 +}; + + + +class QTessellatorPrivate { +public: + struct Vertices; + + QTessellatorPrivate() {} + + QRectF collectAndSortVertices(const QPointF *points, int *maxActiveEdges); + void cancelCoincidingEdges(); + + void emitEdges(QTessellator *tessellator); + void processIntersections(); + void removeEdges(); + void addEdges(); + void addIntersections(); + + struct Vertex : public QTessellator::Vertex + { + int flags; + }; + + struct Intersection + { + Q27Dot5 y; + int edge; + bool operator <(const Intersection &other) const { + if (y != other.y) + return y < other.y; + return edge < other.edge; + } + }; + struct IntersectionLink + { + int next; + int prev; + }; + typedef QMap<Intersection, IntersectionLink> Intersections; + + struct Edge { + Edge(const Vertices &v, int _edge); + int edge; + const Vertex *v0; + const Vertex *v1; + Q27Dot5 y_left; + Q27Dot5 y_right; + signed int winding : 8; + bool mark; + bool free; + bool intersect_left; + bool intersect_right; + bool isLeftOf(const Edge &other, Q27Dot5 y) const; + Q27Dot5 positionAt(Q27Dot5 y) const; + bool intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const; + + }; + + class EdgeSorter + { + public: + EdgeSorter(int _y) : y(_y) {} + bool operator() (const Edge *e1, const Edge *e2); + int y; + }; + + class Scanline { + public: + Scanline(); + ~Scanline(); + + void init(int maxActiveEdges); + void done(); + + int findEdgePosition(Q27Dot5 x, Q27Dot5 y) const; + int findEdgePosition(const Edge &e) const; + int findEdge(int edge) const; + void clearMarks(); + + void swap(int p1, int p2) { + Edge *tmp = edges[p1]; + edges[p1] = edges[p2]; + edges[p2] = tmp; + } + void insert(int pos, const Edge &e); + void removeAt(int pos); + void markEdges(int pos1, int pos2); + + void prepareLine(); + void lineDone(); + + Edge **old; + int old_size; + + Edge **edges; + int size; + + private: + Edge *edge_table; + int first_unused; + int max_edges; + enum { default_alloc = 32 }; + }; + + struct Vertices { + enum { default_alloc = 128 }; + Vertices(); + ~Vertices(); + void init(int maxVertices); + void done(); + Vertex *storage; + Vertex **sorted; + + Vertex *operator[] (int i) { return storage + i; } + const Vertex *operator[] (int i) const { return storage + i; } + int position(const Vertex *v) const { + return v - storage; + } + Vertex *next(Vertex *v) { + ++v; + if (v == storage + nPoints) + v = storage; + return v; + } + const Vertex *next(const Vertex *v) const { + ++v; + if (v == storage + nPoints) + v = storage; + return v; + } + int nextPos(const Vertex *v) const { + ++v; + if (v == storage + nPoints) + return 0; + return v - storage; + } + Vertex *prev(Vertex *v) { + if (v == storage) + v = storage + nPoints; + --v; + return v; + } + const Vertex *prev(const Vertex *v) const { + if (v == storage) + v = storage + nPoints; + --v; + return v; + } + int prevPos(const Vertex *v) const { + if (v == storage) + v = storage + nPoints; + --v; + return v - storage; + } + int nPoints; + int allocated; + }; + Vertices vertices; + Intersections intersections; + Scanline scanline; + bool winding; + Q27Dot5 y; + int currentVertex; + +private: + void addIntersection(const Edge *e1, const Edge *e2); + bool edgeInChain(Intersection i, int edge); +}; + + +QTessellatorPrivate::Edge::Edge(const QTessellatorPrivate::Vertices &vertices, int edge) +{ + this->edge = edge; + intersect_left = intersect_right = true; + mark = false; + free = false; + + v0 = vertices[edge]; + v1 = vertices.next(v0); + + Q_ASSERT(v0->y != v1->y); + + if (v0->y > v1->y) { + qSwap(v0, v1); + winding = -1; + } else { + winding = 1; + } + y_left = y_right = v0->y; +} + +// This is basically the algorithm from graphics gems. The algorithm +// is cubic in the coordinates at one place. Since we use 64bit +// integers, this implies, that the allowed range for our coordinates +// is limited to 21 bits. With 5 bits behind the decimal, this +// implies that differences in coordaintes can range from 2*SHORT_MIN +// to 2*SHORT_MAX, giving us efficiently a coordinate system from +// SHORT_MIN to SHORT_MAX. +// + +// WARNING: It's absolutely critical that the intersect() and isLeftOf() methods use +// exactly the same algorithm to calulate yi. It's also important to be sure the algorithms +// are transitive (ie. the conditions below are true for all input data): +// +// a.intersect(b) == b.intersect(a) +// a.isLeftOf(b) != b.isLeftOf(a) +// +// This is tricky to get right, so be very careful when changing anything in here! + +static inline bool sameSign(qint64 a, qint64 b) { + return (((qint64) ((quint64) a ^ (quint64) b)) >= 0 ); +} + +bool QTessellatorPrivate::Edge::intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const +{ + qint64 a1 = v1->y - v0->y; + qint64 b1 = v0->x - v1->x; + + qint64 a2 = other.v1->y - other.v0->y; + qint64 b2 = other.v0->x - other.v1->x; + + qint64 det = a1 * b2 - a2 * b1; + if (det == 0) + return false; + + qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; + + qint64 r3 = a1 * other.v0->x + b1 * other.v0->y + c1; + qint64 r4 = a1 * other.v1->x + b1 * other.v1->y + c1; + + // Check signs of r3 and r4. If both point 3 and point 4 lie on + // same side of line 1, the line segments do not intersect. + QDEBUG() << " " << r3 << r4; + if (r3 != 0 && r4 != 0 && sameSign( r3, r4 )) + return false; + + qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; + + qint64 r1 = a2 * v0->x + b2 * v0->y + c2; + qint64 r2 = a2 * v1->x + b2 * v1->y + c2; + + // Check signs of r1 and r2. If both point 1 and point 2 lie + // on same side of second line segment, the line segments do not intersect. + QDEBUG() << " " << r1 << r2; + if (r1 != 0 && r2 != 0 && sameSign( r1, r2 )) + return false; + + // The det/2 is to get rounding instead of truncating. It + // is added or subtracted to the numerator, depending upon the + // sign of the numerator. + qint64 offset = det < 0 ? -det : det; + offset >>= 1; + + qint64 num = a2 * c1 - a1 * c2; + *y = ( num < 0 ? num - offset : num + offset ) / det; + + *det_positive = (det > 0); + + return true; +} + +#undef SAME_SIGNS + +bool QTessellatorPrivate::Edge::isLeftOf(const Edge &other, Q27Dot5 y) const +{ +// QDEBUG() << "isLeftOf" << edge << other.edge << y; + qint64 a1 = v1->y - v0->y; + qint64 b1 = v0->x - v1->x; + qint64 a2 = other.v1->y - other.v0->y; + qint64 b2 = other.v0->x - other.v1->x; + + qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; + + qint64 det = a1 * b2 - a2 * b1; + if (det == 0) { + // lines are parallel. Only need to check side of one point + // fixed ordering for coincident edges + qint64 r1 = a2 * v0->x + b2 * v0->y + c2; +// QDEBUG() << "det = 0" << r1; + if (r1 == 0) + return edge < other.edge; + return (r1 < 0); + } + + // not parallel, need to find the y coordinate of the intersection point + qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; + + qint64 offset = det < 0 ? -det : det; + offset >>= 1; + + qint64 num = a2 * c1 - a1 * c2; + qint64 yi = ( num < 0 ? num - offset : num + offset ) / det; +// QDEBUG() << " num=" << num << "offset=" << offset << "det=" << det; + + return ((yi > y) ^ (det < 0)); +} + +static inline bool compareVertex(const QTessellatorPrivate::Vertex *p1, + const QTessellatorPrivate::Vertex *p2) +{ + if (p1->y == p2->y) { + if (p1->x == p2->x) + return p1 < p2; + return p1->x < p2->x; + } + return p1->y < p2->y; +} + +Q27Dot5 QTessellatorPrivate::Edge::positionAt(Q27Dot5 y) const +{ + if (y == v0->y) + return v0->x; + else if (y == v1->y) + return v1->x; + + qint64 d = v1->x - v0->x; + return (v0->x + d*(y - v0->y)/(v1->y-v0->y)); +} + +bool QTessellatorPrivate::EdgeSorter::operator() (const Edge *e1, const Edge *e2) +{ + return e1->isLeftOf(*e2, y); +} + + +QTessellatorPrivate::Scanline::Scanline() +{ + edges = 0; + edge_table = 0; + old = 0; +} + +void QTessellatorPrivate::Scanline::init(int maxActiveEdges) +{ + maxActiveEdges *= 2; + if (!edges || maxActiveEdges > default_alloc) { + max_edges = maxActiveEdges; + int s = qMax(maxActiveEdges + 1, default_alloc + 1); + edges = q_check_ptr((Edge **)realloc(edges, s*sizeof(Edge *))); + edge_table = q_check_ptr((Edge *)realloc(edge_table, s*sizeof(Edge))); + old = q_check_ptr((Edge **)realloc(old, s*sizeof(Edge *))); + } + size = 0; + old_size = 0; + first_unused = 0; + for (int i = 0; i < maxActiveEdges; ++i) + edge_table[i].edge = i+1; + edge_table[maxActiveEdges].edge = -1; +} + +void QTessellatorPrivate::Scanline::done() +{ + if (max_edges > default_alloc) { + free(edges); + free(old); + free(edge_table); + edges = 0; + old = 0; + edge_table = 0; + } +} + +QTessellatorPrivate::Scanline::~Scanline() +{ + free(edges); + free(old); + free(edge_table); +} + +int QTessellatorPrivate::Scanline::findEdgePosition(Q27Dot5 x, Q27Dot5 y) const +{ + int min = 0; + int max = size - 1; + while (min < max) { + int pos = min + ((max - min + 1) >> 1); + Q27Dot5 ax = edges[pos]->positionAt(y); + if (ax > x) { + max = pos - 1; + } else { + min = pos; + } + } + return min; +} + +int QTessellatorPrivate::Scanline::findEdgePosition(const Edge &e) const +{ +// qDebug() << ">> findEdgePosition"; + int min = 0; + int max = size; + while (min < max) { + int pos = min + ((max - min) >> 1); +// qDebug() << " " << min << max << pos << edges[pos]->isLeftOf(e, e.y0); + if (edges[pos]->isLeftOf(e, e.v0->y)) { + min = pos + 1; + } else { + max = pos; + } + } +// qDebug() << "<< findEdgePosition got" << min; + return min; +} + +int QTessellatorPrivate::Scanline::findEdge(int edge) const +{ + for (int i = 0; i < size; ++i) { + int item_edge = edges[i]->edge; + if (item_edge == edge) + return i; + } + //Q_ASSERT(false); + return -1; +} + +void QTessellatorPrivate::Scanline::clearMarks() +{ + for (int i = 0; i < size; ++i) { + edges[i]->mark = false; + edges[i]->intersect_left = false; + edges[i]->intersect_right = false; + } +} + +void QTessellatorPrivate::Scanline::prepareLine() +{ + Edge **end = edges + size; + Edge **e = edges; + Edge **o = old; + while (e < end) { + *o = *e; + ++o; + ++e; + } + old_size = size; +} + +void QTessellatorPrivate::Scanline::lineDone() +{ + Edge **end = old + old_size; + Edge **e = old; + while (e < end) { + if ((*e)->free) { + (*e)->edge = first_unused; + first_unused = (*e - edge_table); + } + ++e; + } +} + +void QTessellatorPrivate::Scanline::insert(int pos, const Edge &e) +{ + Edge *edge = edge_table + first_unused; + first_unused = edge->edge; + Q_ASSERT(first_unused != -1); + *edge = e; + memmove(edges + pos + 1, edges + pos, (size - pos)*sizeof(Edge *)); + edges[pos] = edge; + ++size; +} + +void QTessellatorPrivate::Scanline::removeAt(int pos) +{ + Edge *e = edges[pos]; + e->free = true; + --size; + memmove(edges + pos, edges + pos + 1, (size - pos)*sizeof(Edge *)); +} + +void QTessellatorPrivate::Scanline::markEdges(int pos1, int pos2) +{ + if (pos2 < pos1) + return; + + for (int i = pos1; i <= pos2; ++i) + edges[i]->mark = true; +} + + +QTessellatorPrivate::Vertices::Vertices() +{ + storage = 0; + sorted = 0; + allocated = 0; + nPoints = 0; +} + +QTessellatorPrivate::Vertices::~Vertices() +{ + if (storage) { + free(storage); + free(sorted); + } +} + +void QTessellatorPrivate::Vertices::init(int maxVertices) +{ + if (!storage || maxVertices > allocated) { + int size = qMax((int)default_alloc, maxVertices); + storage = q_check_ptr((Vertex *)realloc(storage, size*sizeof(Vertex))); + sorted = q_check_ptr((Vertex **)realloc(sorted, size*sizeof(Vertex *))); + allocated = maxVertices; + } +} + +void QTessellatorPrivate::Vertices::done() +{ + if (allocated > default_alloc) { + free(storage); + free(sorted); + storage = 0; + sorted = 0; + allocated = 0; + } +} + + + +static inline void fillTrapezoid(Q27Dot5 y1, Q27Dot5 y2, int left, int right, + const QTessellatorPrivate::Vertices &vertices, + QTessellator::Trapezoid *trap) +{ + trap->top = y1; + trap->bottom = y2; + const QTessellatorPrivate::Vertex *v = vertices[left]; + trap->topLeft = v; + trap->bottomLeft = vertices.next(v); + if (trap->topLeft->y > trap->bottomLeft->y) + qSwap(trap->topLeft,trap->bottomLeft); + v = vertices[right]; + trap->topRight = v; + trap->bottomRight = vertices.next(v); + if (trap->topRight->y > trap->bottomRight->y) + qSwap(trap->topRight, trap->bottomRight); +} + +QRectF QTessellatorPrivate::collectAndSortVertices(const QPointF *points, int *maxActiveEdges) +{ + *maxActiveEdges = 0; + Vertex *v = vertices.storage; + Vertex **vv = vertices.sorted; + + qreal xmin(points[0].x()); + qreal xmax(points[0].x()); + qreal ymin(points[0].y()); + qreal ymax(points[0].y()); + + // collect vertex data + Q27Dot5 y_prev = FloatToQ27Dot5(points[vertices.nPoints-1].y()); + Q27Dot5 x_next = FloatToQ27Dot5(points[0].x()); + Q27Dot5 y_next = FloatToQ27Dot5(points[0].y()); + int j = 0; + int i = 0; + while (i < vertices.nPoints) { + Q27Dot5 y_curr = y_next; + + *vv = v; + + v->x = x_next; + v->y = y_next; + v->flags = 0; + + next_point: + + xmin = qMin(xmin, points[i+1].x()); + xmax = qMax(xmax, points[i+1].x()); + ymin = qMin(ymin, points[i+1].y()); + ymax = qMax(ymax, points[i+1].y()); + + y_next = FloatToQ27Dot5(points[i+1].y()); + x_next = FloatToQ27Dot5(points[i+1].x()); + + // skip vertices on top of each other + if (v->x == x_next && v->y == y_next) { + ++i; + if (i < vertices.nPoints) + goto next_point; + Vertex *v0 = vertices.storage; + v0->flags &= ~(LineBeforeStarts|LineBeforeEnds|LineBeforeHorizontal); + if (y_prev < y_curr) + v0->flags |= LineBeforeEnds; + else if (y_prev > y_curr) + v0->flags |= LineBeforeStarts; + else + v0->flags |= LineBeforeHorizontal; + if ((v0->flags & (LineBeforeStarts|LineAfterStarts)) + && !(v0->flags & (LineAfterEnds|LineBeforeEnds))) + *maxActiveEdges += 2; + break; + } + + if (y_prev < y_curr) + v->flags |= LineBeforeEnds; + else if (y_prev > y_curr) + v->flags |= LineBeforeStarts; + else + v->flags |= LineBeforeHorizontal; + + + if (y_curr < y_next) + v->flags |= LineAfterStarts; + else if (y_curr > y_next) + v->flags |= LineAfterEnds; + else + v->flags |= LineAfterHorizontal; + // ### could probably get better limit by looping over sorted list and counting down on ending edges + if ((v->flags & (LineBeforeStarts|LineAfterStarts)) + && !(v->flags & (LineAfterEnds|LineBeforeEnds))) + *maxActiveEdges += 2; + y_prev = y_curr; + ++v; + ++vv; + ++j; + ++i; + } + vertices.nPoints = j; + + QDEBUG() << "maxActiveEdges=" << *maxActiveEdges; + vv = vertices.sorted; + std::sort(vv, vv + vertices.nPoints, compareVertex); + + return QRectF(xmin, ymin, xmax-xmin, ymax-ymin); +} + +struct QCoincidingEdge { + QTessellatorPrivate::Vertex *start; + QTessellatorPrivate::Vertex *end; + bool used; + bool before; + + inline bool operator<(const QCoincidingEdge &e2) const + { + return end->y == e2.end->y ? end->x < e2.end->x : end->y < e2.end->y; + } +}; + +static void cancelEdges(QCoincidingEdge &e1, QCoincidingEdge &e2) +{ + if (e1.before) { + e1.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); + e1.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); + } else { + e1.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); + e1.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); + } + if (e2.before) { + e2.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); + e2.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); + } else { + e2.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); + e2.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); + } + e1.used = e2.used = true; +} + +void QTessellatorPrivate::cancelCoincidingEdges() +{ + Vertex **vv = vertices.sorted; + + QCoincidingEdge *tl = 0; + int tlSize = 0; + + for (int i = 0; i < vertices.nPoints - 1; ++i) { + Vertex *v = vv[i]; + int testListSize = 0; + while (i < vertices.nPoints - 1) { + Vertex *n = vv[i]; + if (v->x != n->x || v->y != n->y) + break; + + if (testListSize > tlSize - 2) { + tlSize = qMax(tlSize*2, 16); + tl = q_check_ptr((QCoincidingEdge *)realloc(tl, tlSize*sizeof(QCoincidingEdge))); + } + if (n->flags & (LineBeforeStarts|LineBeforeHorizontal)) { + tl[testListSize].start = n; + tl[testListSize].end = vertices.prev(n); + tl[testListSize].used = false; + tl[testListSize].before = true; + ++testListSize; + } + if (n->flags & (LineAfterStarts|LineAfterHorizontal)) { + tl[testListSize].start = n; + tl[testListSize].end = vertices.next(n); + tl[testListSize].used = false; + tl[testListSize].before = false; + ++testListSize; + } + ++i; + } + if (!testListSize) + continue; + + std::sort(tl, tl + testListSize); + +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wfor-loop-analysis") + for (int j = 0; j < testListSize; ++j) { + if (tl[j].used) + continue; + + for (int k = j + 1; k < testListSize; ++k) { + if (tl[j].end->x != tl[k].end->x + || tl[j].end->y != tl[k].end->y + || tl[k].used) + break; + + if (!winding || tl[j].before != tl[k].before) { + cancelEdges(tl[j], tl[k]); + break; + } + ++k; + } + ++j; + } +QT_WARNING_POP + } + free(tl); +} + + +void QTessellatorPrivate::emitEdges(QTessellator *tessellator) +{ + //QDEBUG() << "TRAPS:"; + if (!scanline.old_size) + return; + + // emit edges + if (winding) { + // winding fill rule + int w = 0; + + scanline.old[0]->y_left = y; + + for (int i = 0; i < scanline.old_size - 1; ++i) { + Edge *left = scanline.old[i]; + Edge *right = scanline.old[i+1]; + w += left->winding; +// qDebug() << "i=" << i << "edge->winding=" << left->winding << "winding=" << winding; + if (w == 0) { + left->y_right = y; + right->y_left = y; + } else if (!emit_clever || left->mark || right->mark) { + Q27Dot5 top = qMax(left->y_right, right->y_left); + if (top != y) { + QTessellator::Trapezoid trap; + fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); + tessellator->addTrap(trap); +// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; + } + right->y_left = y; + left->y_right = y; + } + left->mark = false; + } + if (scanline.old[scanline.old_size - 1]->mark) { + scanline.old[scanline.old_size - 1]->y_right = y; + scanline.old[scanline.old_size - 1]->mark = false; + } + } else { + // odd-even fill rule + for (int i = 0; i < scanline.old_size; i += 2) { + Edge *left = scanline.old[i]; + Edge *right = scanline.old[i+1]; + if (!emit_clever || left->mark || right->mark) { + Q27Dot5 top = qMax(left->y_right, right->y_left); + if (top != y) { + QTessellator::Trapezoid trap; + fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); + tessellator->addTrap(trap); + } +// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; + left->y_left = y; + left->y_right = y; + right->y_left = y; + right->y_right = y; + left->mark = right->mark = false; + } + } + } +} + + +void QTessellatorPrivate::processIntersections() +{ + QDEBUG() << "PROCESS INTERSECTIONS"; + // process intersections + while (!intersections.isEmpty()) { + Intersections::iterator it = intersections.begin(); + if (it.key().y != y) + break; + + // swap edges + QDEBUG() << " swapping intersecting edges "; + int min = scanline.size; + int max = 0; + Q27Dot5 xmin = INT_MAX; + Q27Dot5 xmax = INT_MIN; + int num = 0; + while (1) { + const Intersection i = it.key(); + int next = it->next; + + int edgePos = scanline.findEdge(i.edge); + if (edgePos >= 0) { + ++num; + min = qMin(edgePos, min); + max = qMax(edgePos, max); + Edge *edge = scanline.edges[edgePos]; + xmin = qMin(xmin, edge->positionAt(y)); + xmax = qMax(xmax, edge->positionAt(y)); + } + Intersection key; + key.y = y; + key.edge = next; + it = intersections.find(key); + intersections.remove(i); + if (it == intersections.end()) + break; + } + if (num < 2) + continue; + + Q_ASSERT(min != max); + QDEBUG() << "sorting between" << min << "and" << max << "xpos=" << xmin << xmax; + while (min > 0 && scanline.edges[min - 1]->positionAt(y) >= xmin) { + QDEBUG() << " adding edge on left"; + --min; + } + while (max < scanline.size - 1 && scanline.edges[max + 1]->positionAt(y) <= xmax) { + QDEBUG() << " adding edge on right"; + ++max; + } + + std::sort(scanline.edges + min, scanline.edges + max + 1, EdgeSorter(y)); +#ifdef DEBUG + for (int i = min; i <= max; ++i) + QDEBUG() << " " << scanline.edges[i]->edge << "at pos" << i; +#endif + for (int i = min; i <= max; ++i) { + Edge *edge = scanline.edges[i]; + edge->intersect_left = true; + edge->intersect_right = true; + edge->mark = true; + } + } +} + +void QTessellatorPrivate::removeEdges() +{ + int cv = currentVertex; + while (cv < vertices.nPoints) { + const Vertex *v = vertices.sorted[cv]; + if (v->y > y) + break; + if (v->flags & LineBeforeEnds) { + QDEBUG() << " removing edge" << vertices.prevPos(v); + int pos = scanline.findEdge(vertices.prevPos(v)); + if (pos == -1) + continue; + scanline.edges[pos]->mark = true; + if (pos > 0) + scanline.edges[pos - 1]->intersect_right = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->intersect_left = true; + scanline.removeAt(pos); + } + if (v->flags & LineAfterEnds) { + QDEBUG() << " removing edge" << vertices.position(v); + int pos = scanline.findEdge(vertices.position(v)); + if (pos == -1) + continue; + scanline.edges[pos]->mark = true; + if (pos > 0) + scanline.edges[pos - 1]->intersect_right = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->intersect_left = true; + scanline.removeAt(pos); + } + ++cv; + } +} + +void QTessellatorPrivate::addEdges() +{ + while (currentVertex < vertices.nPoints) { + const Vertex *v = vertices.sorted[currentVertex]; + if (v->y > y) + break; + if (v->flags & LineBeforeStarts) { + // add new edge + int start = vertices.prevPos(v); + Edge e(vertices, start); + int pos = scanline.findEdgePosition(e); + QDEBUG() << " adding edge" << start << "at position" << pos; + scanline.insert(pos, e); + if (!mark_clever || !(v->flags & LineAfterEnds)) { + if (pos > 0) + scanline.edges[pos - 1]->mark = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->mark = true; + } + } + if (v->flags & LineAfterStarts) { + Edge e(vertices, vertices.position(v)); + int pos = scanline.findEdgePosition(e); + QDEBUG() << " adding edge" << vertices.position(v) << "at position" << pos; + scanline.insert(pos, e); + if (!mark_clever || !(v->flags & LineBeforeEnds)) { + if (pos > 0) + scanline.edges[pos - 1]->mark = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->mark = true; + } + } + if (v->flags & LineAfterHorizontal) { + int pos1 = scanline.findEdgePosition(v->x, v->y); + const Vertex *next = vertices.next(v); + Q_ASSERT(v->y == next->y); + int pos2 = scanline.findEdgePosition(next->x, next->y); + if (pos2 < pos1) + qSwap(pos1, pos2); + if (pos1 > 0) + --pos1; + if (pos2 == scanline.size) + --pos2; + //QDEBUG() << "marking horizontal edge from " << pos1 << "to" << pos2; + scanline.markEdges(pos1, pos2); + } + ++currentVertex; + } +} + +#ifdef DEBUG +static void checkLinkChain(const QTessellatorPrivate::Intersections &intersections, + QTessellatorPrivate::Intersection i) +{ +// qDebug() << " Link chain: "; + int end = i.edge; + while (1) { + QTessellatorPrivate::IntersectionLink l = intersections.value(i); +// qDebug() << " " << i.edge << "next=" << l.next << "prev=" << l.prev; + if (l.next == end) + break; + Q_ASSERT(l.next != -1); + Q_ASSERT(l.prev != -1); + + QTessellatorPrivate::Intersection i2 = i; + i2.edge = l.next; + QTessellatorPrivate::IntersectionLink l2 = intersections.value(i2); + + Q_ASSERT(l2.next != -1); + Q_ASSERT(l2.prev != -1); + Q_ASSERT(l.next == i2.edge); + Q_ASSERT(l2.prev == i.edge); + i = i2; + } +} +#endif + +bool QTessellatorPrivate::edgeInChain(Intersection i, int edge) +{ + int end = i.edge; + while (1) { + if (i.edge == edge) + return true; + IntersectionLink l = intersections.value(i); + if (l.next == end) + break; + Q_ASSERT(l.next != -1); + Q_ASSERT(l.prev != -1); + + Intersection i2 = i; + i2.edge = l.next; + +#ifndef QT_NO_DEBUG + IntersectionLink l2 = intersections.value(i2); + Q_ASSERT(l2.next != -1); + Q_ASSERT(l2.prev != -1); + Q_ASSERT(l.next == i2.edge); + Q_ASSERT(l2.prev == i.edge); +#endif + i = i2; + } + return false; +} + + +void QTessellatorPrivate::addIntersection(const Edge *e1, const Edge *e2) +{ + const IntersectionLink emptyLink = {-1, -1}; + + int next = vertices.nextPos(vertices[e1->edge]); + if (e2->edge == next) + return; + int prev = vertices.prevPos(vertices[e1->edge]); + if (e2->edge == prev) + return; + + Q27Dot5 yi; + bool det_positive; + bool isect = e1->intersect(*e2, &yi, &det_positive); + QDEBUG("checking edges %d and %d", e1->edge, e2->edge); + if (!isect) { + QDEBUG() << " no intersection"; + return; + } + + // don't emit an intersection if it's at the start of a line segment or above us + if (yi <= y) { + if (!det_positive) + return; + QDEBUG() << " ----->>>>>> WRONG ORDER!"; + yi = y; + } + QDEBUG() << " between edges " << e1->edge << "and" << e2->edge << "at point (" + << Q27Dot5ToDouble(yi) << ')'; + + Intersection i1; + i1.y = yi; + i1.edge = e1->edge; + IntersectionLink link1 = intersections.value(i1, emptyLink); + Intersection i2; + i2.y = yi; + i2.edge = e2->edge; + IntersectionLink link2 = intersections.value(i2, emptyLink); + + // new pair of edges + if (link1.next == -1 && link2.next == -1) { + link1.next = link1.prev = i2.edge; + link2.next = link2.prev = i1.edge; + } else if (link1.next == i2.edge || link1.prev == i2.edge + || link2.next == i1.edge || link2.prev == i1.edge) { +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); + Q_ASSERT(edgeInChain(i1, i2.edge)); +#endif + return; + } else if (link1.next == -1 || link2.next == -1) { + if (link2.next == -1) { + qSwap(i1, i2); + qSwap(link1, link2); + } + Q_ASSERT(link1.next == -1); +#ifdef DEBUG + checkLinkChain(intersections, i2); +#endif + // only i2 in list + link1.next = i2.edge; + link1.prev = link2.prev; + link2.prev = i1.edge; + Intersection other; + other.y = yi; + other.edge = link1.prev; + IntersectionLink link = intersections.value(other, emptyLink); + Q_ASSERT(link.next == i2.edge); + Q_ASSERT(link.prev != -1); + link.next = i1.edge; + intersections.insert(other, link); + } else { + bool connected = edgeInChain(i1, i2.edge); + if (connected) + return; +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); +#endif + // both already in some list. Have to make sure they are connected + // this can be done by cutting open the ring(s) after the two eges and + // connecting them again + Intersection other1; + other1.y = yi; + other1.edge = link1.next; + IntersectionLink linko1 = intersections.value(other1, emptyLink); + Intersection other2; + other2.y = yi; + other2.edge = link2.next; + IntersectionLink linko2 = intersections.value(other2, emptyLink); + + linko1.prev = i2.edge; + link2.next = other1.edge; + + linko2.prev = i1.edge; + link1.next = other2.edge; + intersections.insert(other1, linko1); + intersections.insert(other2, linko2); + } + intersections.insert(i1, link1); + intersections.insert(i2, link2); +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); + Q_ASSERT(edgeInChain(i1, i2.edge)); +#endif + return; + +} + + +void QTessellatorPrivate::addIntersections() +{ + if (scanline.size) { + QDEBUG() << "INTERSECTIONS"; + // check marked edges for intersections +#ifdef DEBUG + for (int i = 0; i < scanline.size; ++i) { + Edge *e = scanline.edges[i]; + QDEBUG() << " " << i << e->edge << "isect=(" << e->intersect_left << e->intersect_right + << ')'; + } +#endif + + for (int i = 0; i < scanline.size - 1; ++i) { + Edge *e1 = scanline.edges[i]; + Edge *e2 = scanline.edges[i + 1]; + // check for intersection + if (e1->intersect_right || e2->intersect_left) + addIntersection(e1, e2); + } + } +#if 0 + if (intersections.constBegin().key().y == y) { + QDEBUG() << "----------------> intersection on same line"; + scanline.clearMarks(); + scanline.processIntersections(y, &intersections); + goto redo; + } +#endif +} + + +QTessellator::QTessellator() +{ + d = new QTessellatorPrivate; +} + +QTessellator::~QTessellator() +{ + delete d; +} + +void QTessellator::setWinding(bool w) +{ + d->winding = w; +} + + +QRectF QTessellator::tessellate(const QPointF *points, int nPoints) +{ + Q_ASSERT(points[0] == points[nPoints-1]); + --nPoints; + +#ifdef DEBUG + QDEBUG()<< "POINTS:"; + for (int i = 0; i < nPoints; ++i) { + QDEBUG() << points[i]; + } +#endif + + // collect edges and calculate bounds + d->vertices.nPoints = nPoints; + d->vertices.init(nPoints); + + int maxActiveEdges = 0; + QRectF br = d->collectAndSortVertices(points, &maxActiveEdges); + d->cancelCoincidingEdges(); + +#ifdef DEBUG + QDEBUG() << "nPoints = " << nPoints << "using " << d->vertices.nPoints; + QDEBUG()<< "VERTICES:"; + for (int i = 0; i < d->vertices.nPoints; ++i) { + QDEBUG() << " " << i << ": " + << "point=" << d->vertices.position(d->vertices.sorted[i]) + << "flags=" << d->vertices.sorted[i]->flags + << "pos=(" << Q27Dot5ToDouble(d->vertices.sorted[i]->x) << '/' + << Q27Dot5ToDouble(d->vertices.sorted[i]->y) << ')'; + } +#endif + + d->scanline.init(maxActiveEdges); + d->y = INT_MIN/256; + d->currentVertex = 0; + + while (d->currentVertex < d->vertices.nPoints) { + d->scanline.clearMarks(); + + d->y = d->vertices.sorted[d->currentVertex]->y; + if (!d->intersections.isEmpty()) + d->y = qMin(d->y, d->intersections.constBegin().key().y); + + QDEBUG()<< "===== SCANLINE: y =" << Q27Dot5ToDouble(d->y) << " ====="; + + d->scanline.prepareLine(); + d->processIntersections(); + d->removeEdges(); + d->addEdges(); + d->addIntersections(); + d->emitEdges(this); + d->scanline.lineDone(); + +#ifdef DEBUG + QDEBUG()<< "===== edges:"; + for (int i = 0; i < d->scanline.size; ++i) { + QDEBUG() << " " << d->scanline.edges[i]->edge + << "p0= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v0->x) + << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v0->y) + << ") p1= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v1->x) + << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v1->y) << ')' + << "x=" << Q27Dot5ToDouble(d->scanline.edges[i]->positionAt(d->y)) + << "isLeftOfNext=" + << ((i < d->scanline.size - 1) + ? d->scanline.edges[i]->isLeftOf(*d->scanline.edges[i+1], d->y) + : true); + } +#endif +} + + d->scanline.done(); + d->intersections.clear(); + return br; +} + +// tessellates the given convex polygon +void QTessellator::tessellateConvex(const QPointF *points, int nPoints) +{ + Q_ASSERT(points[0] == points[nPoints-1]); + --nPoints; + + d->vertices.nPoints = nPoints; + d->vertices.init(nPoints); + + for (int i = 0; i < nPoints; ++i) { + d->vertices[i]->x = FloatToQ27Dot5(points[i].x()); + d->vertices[i]->y = FloatToQ27Dot5(points[i].y()); + } + + int left = 0, right = 0; + + int top = 0; + for (int i = 1; i < nPoints; ++i) { + if (d->vertices[i]->y < d->vertices[top]->y) + top = i; + } + + left = (top + nPoints - 1) % nPoints; + right = (top + 1) % nPoints; + + while (d->vertices[left]->x == d->vertices[top]->x && d->vertices[left]->y == d->vertices[top]->y && left != right) + left = (left + nPoints - 1) % nPoints; + + while (d->vertices[right]->x == d->vertices[top]->x && d->vertices[right]->y == d->vertices[top]->y && left != right) + right = (right + 1) % nPoints; + + if (left == right) + return; + + int dir = 1; + + Vertex dLeft = { d->vertices[top]->x - d->vertices[left]->x, + d->vertices[top]->y - d->vertices[left]->y }; + + Vertex dRight = { d->vertices[right]->x - d->vertices[top]->x, + d->vertices[right]->y - d->vertices[top]->y }; + + Q27Dot5 cross = dLeft.x * dRight.y - dLeft.y * dRight.x; + + // flip direction if polygon is clockwise + if (cross < 0 || (cross == 0 && dLeft.x > 0)) { + qSwap(left, right); + dir = -1; + } + + Vertex *lastLeft = d->vertices[top]; + Vertex *lastRight = d->vertices[top]; + + QTessellator::Trapezoid trap; + + while (lastLeft->y == d->vertices[left]->y && left != right) { + lastLeft = d->vertices[left]; + left = (left + nPoints - dir) % nPoints; + } + + while (lastRight->y == d->vertices[right]->y && left != right) { + lastRight = d->vertices[right]; + right = (right + nPoints + dir) % nPoints; + } + + while (true) { + trap.top = qMax(lastRight->y, lastLeft->y); + trap.bottom = qMin(d->vertices[left]->y, d->vertices[right]->y); + trap.topLeft = lastLeft; + trap.topRight = lastRight; + trap.bottomLeft = d->vertices[left]; + trap.bottomRight = d->vertices[right]; + + if (trap.bottom > trap.top) + addTrap(trap); + + if (left == right) + break; + + if (d->vertices[right]->y < d->vertices[left]->y) { + do { + lastRight = d->vertices[right]; + right = (right + nPoints + dir) % nPoints; + } + while (lastRight->y == d->vertices[right]->y && left != right); + } else { + do { + lastLeft = d->vertices[left]; + left = (left + nPoints - dir) % nPoints; + } + while (lastLeft->y == d->vertices[left]->y && left != right); + } + } +} + +// tessellates the stroke of the line from a_ to b_ with the given width and a flat cap +void QTessellator::tessellateRect(const QPointF &a_, const QPointF &b_, qreal width) +{ + Vertex a = { FloatToQ27Dot5(a_.x()), FloatToQ27Dot5(a_.y()) }; + Vertex b = { FloatToQ27Dot5(b_.x()), FloatToQ27Dot5(b_.y()) }; + + QPointF pa = a_, pb = b_; + + if (a.y > b.y) { + qSwap(a, b); + qSwap(pa, pb); + } + + Vertex delta = { b.x - a.x, b.y - a.y }; + + if (delta.x == 0 && delta.y == 0) + return; + + qreal hw = 0.5 * width; + + if (delta.x == 0) { + Q27Dot5 halfWidth = FloatToQ27Dot5(hw); + + if (halfWidth == 0) + return; + + Vertex topLeft = { a.x - halfWidth, a.y }; + Vertex topRight = { a.x + halfWidth, a.y }; + Vertex bottomLeft = { a.x - halfWidth, b.y }; + Vertex bottomRight = { a.x + halfWidth, b.y }; + + QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; + addTrap(trap); + } else if (delta.y == 0) { + Q27Dot5 halfWidth = FloatToQ27Dot5(hw); + + if (halfWidth == 0) + return; + + if (a.x > b.x) + qSwap(a.x, b.x); + + Vertex topLeft = { a.x, a.y - halfWidth }; + Vertex topRight = { b.x, a.y - halfWidth }; + Vertex bottomLeft = { a.x, a.y + halfWidth }; + Vertex bottomRight = { b.x, a.y + halfWidth }; + + QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; + addTrap(trap); + } else { + QPointF perp(pb.y() - pa.y(), pa.x() - pb.x()); + qreal length = qSqrt(perp.x() * perp.x() + perp.y() * perp.y()); + + if (qFuzzyIsNull(length)) + return; + + // need the half of the width + perp *= hw / length; + + QPointF pta = pa + perp; + QPointF ptb = pa - perp; + QPointF ptc = pb - perp; + QPointF ptd = pb + perp; + + Vertex ta = { FloatToQ27Dot5(pta.x()), FloatToQ27Dot5(pta.y()) }; + Vertex tb = { FloatToQ27Dot5(ptb.x()), FloatToQ27Dot5(ptb.y()) }; + Vertex tc = { FloatToQ27Dot5(ptc.x()), FloatToQ27Dot5(ptc.y()) }; + Vertex td = { FloatToQ27Dot5(ptd.x()), FloatToQ27Dot5(ptd.y()) }; + + if (ta.y < tb.y) { + if (tb.y < td.y) { + QTessellator::Trapezoid top = { ta.y, tb.y, &ta, &tb, &ta, &td }; + QTessellator::Trapezoid bottom = { td.y, tc.y, &tb, &tc, &td, &tc }; + addTrap(top); + addTrap(bottom); + + QTessellator::Trapezoid middle = { tb.y, td.y, &tb, &tc, &ta, &td }; + addTrap(middle); + } else { + QTessellator::Trapezoid top = { ta.y, td.y, &ta, &tb, &ta, &td }; + QTessellator::Trapezoid bottom = { tb.y, tc.y, &tb, &tc, &td, &tc }; + addTrap(top); + addTrap(bottom); + + if (tb.y != td.y) { + QTessellator::Trapezoid middle = { td.y, tb.y, &ta, &tb, &td, &tc }; + addTrap(middle); + } + } + } else { + if (ta.y < tc.y) { + QTessellator::Trapezoid top = { tb.y, ta.y, &tb, &tc, &tb, &ta }; + QTessellator::Trapezoid bottom = { tc.y, td.y, &tc, &td, &ta, &td }; + addTrap(top); + addTrap(bottom); + + QTessellator::Trapezoid middle = { ta.y, tc.y, &tb, &tc, &ta, &td }; + addTrap(middle); + } else { + QTessellator::Trapezoid top = { tb.y, tc.y, &tb, &tc, &tb, &ta }; + QTessellator::Trapezoid bottom = { ta.y, td.y, &tc, &td, &ta, &td }; + addTrap(top); + addTrap(bottom); + + if (ta.y != tc.y) { + QTessellator::Trapezoid middle = { tc.y, ta.y, &tc, &td, &tb, &ta }; + addTrap(middle); + } + } + } + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h new file mode 100644 index 0000000000..65ae6bdc41 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTESSELATOR_P_H +#define QTESSELATOR_P_H + +#include <QPoint> +#include <QRect> + +QT_BEGIN_NAMESPACE + +class QTessellatorPrivate; + +typedef int Q27Dot5; +#define Q27Dot5ToDouble(i) ((i)/32.) +#define FloatToQ27Dot5(i) (int)((i) * 32) +#define IntToQ27Dot5(i) ((i) << 5) +#define Q27Dot5ToXFixed(i) ((i) << 11) +#define Q27Dot5Factor 32 + +class QTessellator { +public: + QTessellator(); + virtual ~QTessellator(); + + QRectF tessellate(const QPointF *points, int nPoints); + void tessellateConvex(const QPointF *points, int nPoints); + void tessellateRect(const QPointF &a, const QPointF &b, qreal width); + + void setWinding(bool w); + + struct Vertex { + Q27Dot5 x; + Q27Dot5 y; + }; + struct Trapezoid { + Q27Dot5 top; + Q27Dot5 bottom; + const Vertex *topLeft; + const Vertex *bottomLeft; + const Vertex *topRight; + const Vertex *bottomRight; + }; + virtual void addTrap(const Trapezoid &trap) = 0; + +private: + friend class QTessellatorPrivate; + QTessellatorPrivate *d; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp new file mode 100644 index 0000000000..ccb421d868 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qxcbconnection.h" +#include "qcolormap_x11_p.h" +#include "qxcbnativepainting.h" +#include "qt_x11_p.h" + +QT_BEGIN_NAMESPACE + +QXcbX11Data *qt_x11Data = Q_NULLPTR; + +void qt_xcb_native_x11_info_init(QXcbConnection *conn) +{ + qt_x11Data = new QXcbX11Data; + X11->display = static_cast<Display *>(conn->xlib_display()); + X11->defaultScreen = DefaultScreen(X11->display); + X11->screenCount = ScreenCount(X11->display); + + X11->screens = new QX11InfoData[X11->screenCount]; + X11->argbVisuals = new Visual *[X11->screenCount]; + X11->argbColormaps = new Colormap[X11->screenCount]; + + for (int s = 0; s < X11->screenCount; s++) { + QX11InfoData *screen = X11->screens + s; + //screen->ref = 1; // ensures it doesn't get deleted + screen->screen = s; + + int widthMM = DisplayWidthMM(X11->display, s); + if (widthMM != 0) { + screen->dpiX = (DisplayWidth(X11->display, s) * 254 + widthMM * 5) / (widthMM * 10); + } else { + screen->dpiX = 72; + } + + int heightMM = DisplayHeightMM(X11->display, s); + if (heightMM != 0) { + screen->dpiY = (DisplayHeight(X11->display, s) * 254 + heightMM * 5) / (heightMM * 10); + } else { + screen->dpiY = 72; + } + + X11->argbVisuals[s] = 0; + X11->argbColormaps[s] = 0; + } + + X11->use_xrender = conn->hasXRender() && !qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING_NO_XRENDER"); + +#if QT_CONFIG(xrender) + memset(X11->solid_fills, 0, sizeof(X11->solid_fills)); + for (int i = 0; i < X11->solid_fill_count; ++i) + X11->solid_fills[i].screen = -1; + memset(X11->pattern_fills, 0, sizeof(X11->pattern_fills)); + for (int i = 0; i < X11->pattern_fill_count; ++i) + X11->pattern_fills[i].screen = -1; +#endif + + QXcbColormap::initialize(); + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + // XRender is supported, let's see if we have a PictFormat for the + // default visual + XRenderPictFormat *format = + XRenderFindVisualFormat(X11->display, + (Visual *) QXcbX11Info::appVisual(X11->defaultScreen)); + + if (!format) { + X11->use_xrender = false; + } + } +#endif // QT_CONFIG(xrender) +} + +QVector<XRectangle> qt_region_to_xrectangles(const QRegion &r) +{ + const int numRects = r.rectCount(); + const QVector<QRect> input = r.rects(); + QVector<XRectangle> output(numRects); + for (int i = 0; i < numRects; ++i) { + const QRect &in = input[i]; + XRectangle &out = output[i]; + out.x = qMax(SHRT_MIN, in.x()); + out.y = qMax(SHRT_MIN, in.y()); + out.width = qMin((int)USHRT_MAX, in.width()); + out.height = qMin((int)USHRT_MAX, in.height()); + } + return output; +} + +class QXcbX11InfoData : public QSharedData, public QX11InfoData +{}; + +QXcbX11Info::QXcbX11Info() + : d(Q_NULLPTR) +{} + +QXcbX11Info::~QXcbX11Info() +{} + +QXcbX11Info::QXcbX11Info(const QXcbX11Info &other) + : d(other.d) +{} + +QXcbX11Info &QXcbX11Info::operator=(const QXcbX11Info &other) +{ + d = other.d; + return *this; +} + +QXcbX11Info QXcbX11Info::fromScreen(int screen) +{ + QXcbX11InfoData *xd = new QXcbX11InfoData; + xd->screen = screen; + xd->depth = QXcbX11Info::appDepth(screen); + xd->cells = QXcbX11Info::appCells(screen); + xd->colormap = QXcbX11Info::appColormap(screen); + xd->defaultColormap = QXcbX11Info::appDefaultColormap(screen); + xd->visual = (Visual *)QXcbX11Info::appVisual(screen); + xd->defaultVisual = QXcbX11Info::appDefaultVisual(screen); + + QXcbX11Info info; + info.d = xd; + return info; +} + +void QXcbX11Info::setDepth(int depth) +{ + if (!d) + *this = fromScreen(appScreen()); + + d->depth = depth; +} + +Display *QXcbX11Info::display() +{ + return X11 ? X11->display : 0; +} + +int QXcbX11Info::screen() const +{ + return d ? d->screen : QXcbX11Info::appScreen(); +} + +int QXcbX11Info::depth() const +{ + return d ? d->depth : QXcbX11Info::appDepth(); +} + +Colormap QXcbX11Info::colormap() const +{ + return d ? d->colormap : QXcbX11Info::appColormap(); +} + +void *QXcbX11Info::visual() const +{ + return d ? d->visual : QXcbX11Info::appVisual(); +} + +void QXcbX11Info::setVisual(void *visual) +{ + if (!d) + *this = fromScreen(appScreen()); + + d->visual = (Visual *) visual; +} + +int QXcbX11Info::appScreen() +{ + return X11 ? X11->defaultScreen : 0; +} + +int QXcbX11Info::appDepth(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].depth : 32; +} + +int QXcbX11Info::appCells(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].cells : 0; +} + +Colormap QXcbX11Info::appColormap(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].colormap : 0; +} + +void *QXcbX11Info::appVisual(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].visual : 0; +} + +Window QXcbX11Info::appRootWindow(int screen) +{ + return X11 ? RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen) : 0; +} + +bool QXcbX11Info::appDefaultColormap(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultColormap : true; +} + +bool QXcbX11Info::appDefaultVisual(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultVisual : true; +} + +int QXcbX11Info::appDpiX(int screen) +{ + if (!X11) + return 75; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return 0; + return X11->screens[screen].dpiX; +} + +int QXcbX11Info::appDpiY(int screen) +{ + if (!X11) + return 75; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return 0; + return X11->screens[screen].dpiY; +} + +#if QT_CONFIG(xrender) +Picture QXcbX11Data::getSolidFill(int screen, const QColor &c) +{ + if (!X11->use_xrender) + return XNone; + + XRenderColor color = preMultiply(c); + for (int i = 0; i < X11->solid_fill_count; ++i) { + if (X11->solid_fills[i].screen == screen + && X11->solid_fills[i].color.alpha == color.alpha + && X11->solid_fills[i].color.red == color.red + && X11->solid_fills[i].color.green == color.green + && X11->solid_fills[i].color.blue == color.blue) + return X11->solid_fills[i].picture; + } + // none found, replace one + int i = qrand() % 16; + + if (X11->solid_fills[i].screen != screen && X11->solid_fills[i].picture) { + XRenderFreePicture (X11->display, X11->solid_fills[i].picture); + X11->solid_fills[i].picture = 0; + } + + if (!X11->solid_fills[i].picture) { + Pixmap pixmap = XCreatePixmap (X11->display, RootWindow (X11->display, screen), 1, 1, 32); + XRenderPictureAttributes attrs; + attrs.repeat = True; + X11->solid_fills[i].picture = XRenderCreatePicture (X11->display, pixmap, + XRenderFindStandardFormat(X11->display, PictStandardARGB32), + CPRepeat, &attrs); + XFreePixmap (X11->display, pixmap); + } + + X11->solid_fills[i].color = color; + X11->solid_fills[i].screen = screen; + XRenderFillRectangle (X11->display, PictOpSrc, X11->solid_fills[i].picture, &color, 0, 0, 1, 1); + return X11->solid_fills[i].picture; +} + +XRenderColor QXcbX11Data::preMultiply(const QColor &c) +{ + XRenderColor color; + const uint A = c.alpha(), + R = c.red(), + G = c.green(), + B = c.blue(); + color.alpha = (A | A << 8); + color.red = (R | R << 8) * color.alpha / 0x10000; + color.green = (G | G << 8) * color.alpha / 0x10000; + color.blue = (B | B << 8) * color.alpha / 0x10000; + return color; +} +#endif // QT_CONFIG(xrender) + + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h new file mode 100644 index 0000000000..f3011286c9 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QXCBNATIVEPAINTING_H +#define QXCBNATIVEPAINTING_H + +#include <QSharedDataPointer> +#include "qt_x11_p.h" + +typedef struct _FcPattern FcPattern; +typedef unsigned long XID; +typedef XID Colormap; +typedef XID Window; +typedef struct _XDisplay Display; + +QT_BEGIN_NAMESPACE + +class QXcbConnection; +class QPixmap; + +void qt_xcb_native_x11_info_init(QXcbConnection *conn); +QVector<XRectangle> qt_region_to_xrectangles(const QRegion &r); + +class QXcbX11InfoData; +class QXcbX11Info +{ +public: + QXcbX11Info(); + ~QXcbX11Info(); + QXcbX11Info(const QXcbX11Info &other); + QXcbX11Info &operator=(const QXcbX11Info &other); + + static QXcbX11Info fromScreen(int screen); + static Display *display(); + + int depth() const; + void setDepth(int depth); + + int screen() const; + Colormap colormap() const; + + void *visual() const; + void setVisual(void *visual); + + static int appScreen(); + static int appDepth(int screen = -1); + static int appCells(int screen = -1); + static Colormap appColormap(int screen = -1); + static void *appVisual(int screen = -1); + static Window appRootWindow(int screen = -1); + static bool appDefaultColormap(int screen = -1); + static bool appDefaultVisual(int screen = -1); + static int appDpiX(int screen = -1); + static int appDpiY(int screen = -1); + +private: + QSharedDataPointer<QXcbX11InfoData> d; + + friend class QX11PaintEngine; + friend class QX11PlatformPixmap; + friend void qt_x11SetScreen(QPixmap &pixmap, int screen); +}; + +QT_END_NAMESPACE + +#endif // QXCBNATIVEPAINTING_H diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp index a419caf0fc..420d1ac7c5 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp +++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp @@ -150,8 +150,6 @@ QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QI , m_gc_drawable(0) , m_xcb_pixmap(0) { - Q_XCB_NOOP(connection()); - const xcb_format_t *fmt = connection()->formatForDepth(depth); Q_ASSERT(fmt); @@ -206,11 +204,11 @@ QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QI if (!hasShm()) { m_xcb_pixmap = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_pixmap(xcb_connection(), - m_xcb_image->depth, - m_xcb_pixmap, - screen->screen()->root, - m_xcb_image->width, m_xcb_image->height)); + xcb_create_pixmap(xcb_connection(), + m_xcb_image->depth, + m_xcb_pixmap, + screen->screen()->root, + m_xcb_image->width, m_xcb_image->height); } } @@ -234,13 +232,13 @@ bool QXcbShmImage::scroll(const QRegion &area, int dx, int dy) const QRect bounds(QPoint(0, 0), size()); for (const QRect &src : area) { const QRect dst = src.translated(delta).intersected(bounds); - Q_XCB_CALL(xcb_copy_area(xcb_connection(), - m_xcb_pixmap, - m_xcb_pixmap, - m_gc, - src.x(), src.y(), - dst.x(), dst.y(), - dst.width(), dst.height())); + xcb_copy_area(xcb_connection(), + m_xcb_pixmap, + m_xcb_pixmap, + m_gc, + src.x(), src.y(), + dst.x(), dst.y(), + dst.width(), dst.height()); } } @@ -251,7 +249,7 @@ void QXcbShmImage::destroy() { const int segmentSize = m_xcb_image ? (m_xcb_image->stride * m_xcb_image->height) : 0; if (segmentSize && m_shm_info.shmaddr) - Q_XCB_CALL(xcb_shm_detach(xcb_connection(), m_shm_info.shmseg)); + xcb_shm_detach(xcb_connection(), m_shm_info.shmseg); if (segmentSize) { if (m_shm_info.shmaddr) { @@ -265,12 +263,12 @@ void QXcbShmImage::destroy() xcb_image_destroy(m_xcb_image); if (m_gc) - Q_XCB_CALL(xcb_free_gc(xcb_connection(), m_gc)); + xcb_free_gc(xcb_connection(), m_gc); delete m_graphics_buffer; m_graphics_buffer = Q_NULLPTR; if (m_xcb_pixmap) { - Q_XCB_CALL(xcb_free_pixmap(xcb_connection(), m_xcb_pixmap)); + xcb_free_pixmap(xcb_connection(), m_xcb_pixmap); m_xcb_pixmap = 0; } } @@ -279,13 +277,13 @@ void QXcbShmImage::ensureGC(xcb_drawable_t dst) { if (m_gc_drawable != dst) { if (m_gc) - Q_XCB_CALL(xcb_free_gc(xcb_connection(), m_gc)); + xcb_free_gc(xcb_connection(), m_gc); static const uint32_t mask = XCB_GC_GRAPHICS_EXPOSURES; static const uint32_t values[] = { 0 }; m_gc = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_gc(xcb_connection(), m_gc, dst, mask, values)); + xcb_create_gc(xcb_connection(), m_gc, dst, mask, values); m_gc_drawable = dst; } @@ -425,10 +423,7 @@ void QXcbShmImage::setClip(const QRegion ®ion) if (region.isEmpty()) { static const uint32_t mask = XCB_GC_CLIP_MASK; static const uint32_t values[] = { XCB_NONE }; - Q_XCB_CALL(xcb_change_gc(xcb_connection(), - m_gc, - mask, - values)); + xcb_change_gc(xcb_connection(), m_gc, mask, values); } else { const QVector<QRect> qrects = region.rects(); QVector<xcb_rectangle_t> xcb_rects(qrects.size()); @@ -440,18 +435,16 @@ void QXcbShmImage::setClip(const QRegion ®ion) xcb_rects[i].height = qrects[i].height(); } - Q_XCB_CALL(xcb_set_clip_rectangles(xcb_connection(), - XCB_CLIP_ORDERING_YX_BANDED, - m_gc, - 0, 0, - xcb_rects.size(), xcb_rects.constData())); + xcb_set_clip_rectangles(xcb_connection(), + XCB_CLIP_ORDERING_YX_BANDED, + m_gc, + 0, 0, + xcb_rects.size(), xcb_rects.constData()); } } void QXcbShmImage::put(xcb_drawable_t dst, const QRegion ®ion, const QPoint &offset) { - Q_XCB_NOOP(connection()); - ensureGC(dst); setClip(region); @@ -460,33 +453,32 @@ void QXcbShmImage::put(xcb_drawable_t dst, const QRegion ®ion, const QPoint & const QRect source = bounds.translated(offset); if (hasShm()) { - Q_XCB_CALL(xcb_shm_put_image(xcb_connection(), - dst, - m_gc, - m_xcb_image->width, - m_xcb_image->height, - source.x(), source.y(), - source.width(), source.height(), - target.x(), target.y(), - m_xcb_image->depth, - m_xcb_image->format, - 0, // send event? - m_shm_info.shmseg, - m_xcb_image->data - m_shm_info.shmaddr)); + xcb_shm_put_image(xcb_connection(), + dst, + m_gc, + m_xcb_image->width, + m_xcb_image->height, + source.x(), source.y(), + source.width(), source.height(), + target.x(), target.y(), + m_xcb_image->depth, + m_xcb_image->format, + 0, // send event? + m_shm_info.shmseg, + m_xcb_image->data - m_shm_info.shmaddr); m_dirtyShm |= region.translated(offset); } else { flushPixmap(region); - Q_XCB_CALL(xcb_copy_area(xcb_connection(), - m_xcb_pixmap, - dst, - m_gc, - source.x(), source.y(), - target.x(), target.y(), - source.width(), source.height())); + xcb_copy_area(xcb_connection(), + m_xcb_pixmap, + dst, + m_gc, + source.x(), source.y(), + target.x(), target.y(), + source.width(), source.height()); } setClip(QRegion()); - Q_XCB_NOOP(connection()); } void QXcbShmImage::preparePaint(const QRegion ®ion) @@ -592,8 +584,6 @@ void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoin if (bounds.isNull()) return; - Q_XCB_NOOP(connection()); - QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); if (!platformWindow) { qWarning("QXcbBackingStore::flush: QWindow has no platform window (QTBUG-32681)"); @@ -602,8 +592,6 @@ void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoin m_image->put(platformWindow->xcb_window(), clipped, offset); - Q_XCB_NOOP(connection()); - if (platformWindow->needsSync()) platformWindow->updateSyncRequestCounter(); else @@ -612,12 +600,10 @@ void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoin #ifndef QT_NO_OPENGL void QXcbBackingStore::composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, - QPlatformTextureList *textures, QOpenGLContext *context, + QPlatformTextureList *textures, bool translucentBackground) { - QPlatformBackingStore::composeAndFlush(window, region, offset, textures, context, translucentBackground); - - Q_XCB_NOOP(connection()); + QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground); QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); if (platformWindow->needsSync()) { @@ -632,7 +618,6 @@ void QXcbBackingStore::resize(const QSize &size, const QRegion &) { if (m_image && size == m_image->size()) return; - Q_XCB_NOOP(connection()); QXcbScreen *screen = static_cast<QXcbScreen *>(window()->screen()->handle()); QPlatformWindow *pw = window()->handle(); @@ -649,7 +634,6 @@ void QXcbBackingStore::resize(const QSize &size, const QRegion &) if (win->imageNeedsRgbSwap()) { m_rgbImage = QImage(size, win->imageFormat()); } - Q_XCB_NOOP(connection()); } bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy) diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.h b/src/plugins/platforms/xcb/qxcbbackingstore.h index 94b5994004..2e8fbfb7fa 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.h +++ b/src/plugins/platforms/xcb/qxcbbackingstore.h @@ -61,7 +61,7 @@ public: void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; #ifndef QT_NO_OPENGL void composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, - QPlatformTextureList *textures, QOpenGLContext *context, + QPlatformTextureList *textures, bool translucentBackground) override; #endif QImage toImage() const override; diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp index 01b3bca0d2..30ab669432 100644 --- a/src/plugins/platforms/xcb/qxcbclipboard.cpp +++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp @@ -278,22 +278,22 @@ QXcbClipboard::QXcbClipboard(QXcbConnection *c) #ifndef QT_NO_DEBUG QByteArray ba("Qt clipboard window"); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_owner, - atom(QXcbAtom::_NET_WM_NAME), - atom(QXcbAtom::UTF8_STRING), - 8, - ba.length(), - ba.constData())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_owner, + atom(QXcbAtom::_NET_WM_NAME), + atom(QXcbAtom::UTF8_STRING), + 8, + ba.length(), + ba.constData()); #endif if (connection()->hasXFixes()) { const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; - Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, XCB_ATOM_PRIMARY, mask)); - Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD), mask)); + xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, XCB_ATOM_PRIMARY, mask); + xcb_xfixes_select_selection_input_checked(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD), mask); } } @@ -305,8 +305,7 @@ QXcbClipboard::~QXcbClipboard() m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) { // First we check if there is a clipboard manager. - xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER)); - xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER)); if (reply && reply->owner != XCB_NONE) { // we delete the property so the manager saves all TARGETS. xcb_delete_property(xcb_connection(), m_owner, atom(QXcbAtom::_QT_SELECTION)); @@ -320,7 +319,6 @@ QXcbClipboard::~QXcbClipboard() "clipboard manager in a reasonable time"); } } - free(reply); } if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection]) @@ -459,26 +457,26 @@ xcb_window_t QXcbClipboard::requestor() const QXcbClipboard *that = const_cast<QXcbClipboard *>(this); xcb_window_t window = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_window(xcb_connection(), - XCB_COPY_FROM_PARENT, // depth -- same as root - window, // window id - platformScreen->screen()->root, // parent window id - x, y, w, h, - 0, // border width - XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class - platformScreen->screen()->root_visual, // visual - 0, // value mask - 0)); // value list + xcb_create_window(xcb_connection(), + XCB_COPY_FROM_PARENT, // depth -- same as root + window, // window id + platformScreen->screen()->root, // parent window id + x, y, w, h, + 0, // border width + XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class + platformScreen->screen()->root_visual, // visual + 0, // value mask + 0); // value list #ifndef QT_NO_DEBUG QByteArray ba("Qt clipboard requestor window"); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - window, - atom(QXcbAtom::_NET_WM_NAME), - atom(QXcbAtom::UTF8_STRING), - 8, - ba.length(), - ba.constData())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + window, + atom(QXcbAtom::_NET_WM_NAME), + atom(QXcbAtom::UTF8_STRING), + 8, + ba.length(), + ba.constData()); #endif uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE; @@ -759,17 +757,14 @@ bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, format = &dummy_format; // Don't read anything, just get the size of the property data - xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0)); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0); if (!reply || reply->type == XCB_NONE) { - free(reply); buffer->resize(0); return false; } *type = reply->type; *format = reply->format; bytes_left = reply->bytes_after; - free(reply); int offset = 0, buffer_offset = 0; @@ -784,17 +779,15 @@ bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, while (bytes_left) { // more to read... - xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, offset, maxsize/4)); - reply = xcb_get_property_reply(xcb_connection(), cookie, 0); - if (!reply || reply->type == XCB_NONE) { - free(reply); + reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, win, property, XCB_GET_PROPERTY_TYPE_ANY, offset, maxsize/4); + if (!reply || reply->type == XCB_NONE) break; - } + *type = reply->type; *format = reply->format; bytes_left = reply->bytes_after; - char *data = (char *)xcb_get_property_value(reply); - int length = xcb_get_property_value_length(reply); + char *data = (char *)xcb_get_property_value(reply.get()); + int length = xcb_get_property_value_length(reply.get()); // Here we check if we get a buffer overflow and tries to // recover -- this shouldn't normally happen, but it doesn't @@ -814,7 +807,6 @@ bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property, // offset is specified in 32-bit multiples offset += length / 4; } - free(reply); } } @@ -891,13 +883,9 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t win, int return e; if (checkManager) { - xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER)); - xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(xcb_connection(), cookie, 0); - if (!reply || reply->owner == XCB_NONE) { - free(reply); + auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER)); + if (!reply || reply->owner == XCB_NONE) return 0; - } - free(reply); } // process other clipboard events, since someone is probably requesting data from us diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index d36a14b920..536c709dbe 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -109,6 +109,9 @@ Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input") 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(lcQpaPeeker, "qt.qpa.peeker") // this event type was added in libxcb 1.10, // but we support also older version @@ -148,8 +151,18 @@ static const char * const xcbConnectionErrors[] = { "Error during FD passing" /* XCB_CONN_CLOSED_FDPASSING_FAILED */ }; -static int nullErrorHandler(Display *, XErrorEvent *) +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; } @@ -243,11 +256,8 @@ void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event) } 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) { - xcb_randr_get_output_info_cookie_t outputInfoCookie = - xcb_randr_get_output_info(xcb_connection(), output.output, output.config_timestamp); - QScopedPointer<xcb_randr_get_output_info_reply_t, QScopedPointerPodDeleter> outputInfo( - xcb_randr_get_output_info_reply(xcb_connection(), outputInfoCookie, NULL)); - + 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) { @@ -261,12 +271,12 @@ void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event) if (screen) { QString nameWas = screen->name(); // Transform the fake screen into a physical screen - screen->setOutput(output.output, outputInfo.data()); + 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.data()); + screen = createScreen(virtualDesktop, output, outputInfo.get()); qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled"; } QHighDpiScaling::updateHighDpiScaling(); @@ -274,10 +284,8 @@ void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event) } else if (screen) { if (output.crtc == XCB_NONE && output.mode == XCB_NONE) { // Screen has been disabled - xcb_randr_get_output_info_cookie_t outputInfoCookie = - xcb_randr_get_output_info(xcb_connection(), output.output, output.config_timestamp); - QScopedPointer<xcb_randr_get_output_info_reply_t, QScopedPointerPodDeleter> outputInfo( - xcb_randr_get_output_info_reply(xcb_connection(), outputInfoCookie, NULL)); + 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); @@ -299,16 +307,10 @@ void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event) bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output) { - xcb_generic_error_t *error = 0; - xcb_randr_get_output_primary_cookie_t primaryCookie = - xcb_randr_get_output_primary(xcb_connection(), rootWindow); - QScopedPointer<xcb_randr_get_output_primary_reply_t, QScopedPointerPodDeleter> primary ( - xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, &error)); - if (!primary || error) { + 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"); - free(error); - error = NULL; - } + const bool isPrimary = primary ? (primary->output == output) : false; return isPrimary; @@ -404,72 +406,59 @@ void QXcbConnection::initializeScreens() m_virtualDesktops.append(virtualDesktop); QList<QPlatformScreen *> siblings; if (has_randr_extension) { - xcb_generic_error_t *error = NULL; // 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. - QScopedPointer<xcb_randr_get_screen_resources_reply_t, QScopedPointerPodDeleter> resources; - xcb_randr_get_screen_resources_current_cookie_t resourcesCookie = - xcb_randr_get_screen_resources_current(xcb_connection(), xcbScreen->root); - QScopedPointer<xcb_randr_get_screen_resources_current_reply_t, QScopedPointerPodDeleter> resources_current( - xcb_randr_get_screen_resources_current_reply(xcb_connection(), resourcesCookie, &error)); - if (!resources_current || error) { + 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"); - free(error); } else { xcb_timestamp_t timestamp = 0; xcb_randr_output_t *outputs = Q_NULLPTR; - int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources_current.data()); + 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.data()); + outputs = xcb_randr_get_screen_resources_current_outputs(resources_current.get()); } else { - xcb_randr_get_screen_resources_cookie_t resourcesCookie = - xcb_randr_get_screen_resources(xcb_connection(), xcbScreen->root); - resources.reset(xcb_randr_get_screen_resources_reply(xcb_connection(), resourcesCookie, &error)); - if (!resources || error) { + auto resources = Q_XCB_REPLY(xcb_randr_get_screen_resources, + xcb_connection(), xcbScreen->root); + if (!resources) { qWarning("failed to get the screen resources"); - free(error); } else { timestamp = resources->config_timestamp; - outputCount = xcb_randr_get_screen_resources_outputs_length(resources.data()); - outputs = xcb_randr_get_screen_resources_outputs(resources.data()); + outputCount = xcb_randr_get_screen_resources_outputs_length(resources.get()); + outputs = xcb_randr_get_screen_resources_outputs(resources.get()); } } if (outputCount) { - xcb_randr_get_output_primary_cookie_t primaryCookie = - xcb_randr_get_output_primary(xcb_connection(), xcbScreen->root); - QScopedPointer<xcb_randr_get_output_primary_reply_t, QScopedPointerPodDeleter> primary( - xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, &error)); - if (!primary || error) { + 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"); - free(error); } else { for (int i = 0; i < outputCount; i++) { - QScopedPointer<xcb_randr_get_output_info_reply_t, QScopedPointerPodDeleter> output( - xcb_randr_get_output_info_reply(xcb_connection(), - xcb_randr_get_output_info_unchecked(xcb_connection(), outputs[i], timestamp), NULL)); - + auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info, + xcb_connection(), outputs[i], timestamp); // Invalid, disconnected or disabled output - if (output == NULL) + 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.data()), - xcb_randr_get_output_info_name_length(output.data())))); + 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.data()), - xcb_randr_get_output_info_name_length(output.data())))); + 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.data()); + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputs[i], output.get()); siblings << screen; m_screens << screen; @@ -492,12 +481,9 @@ void QXcbConnection::initializeScreens() } } else if (has_xinerama_extension) { // Xinerama is available - xcb_xinerama_query_screens_cookie_t cookie = xcb_xinerama_query_screens(m_connection); - xcb_xinerama_query_screens_reply_t *screens = xcb_xinerama_query_screens_reply(m_connection, - cookie, - Q_NULLPTR); + 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); + 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, @@ -507,7 +493,6 @@ void QXcbConnection::initializeScreens() m_screens << screen; xcb_xinerama_screen_info_next(&it); } - free(screens); } } if (siblings.isEmpty()) { @@ -664,11 +649,6 @@ QXcbConnection::~QXcbConnection() #ifndef QT_NO_DRAGANDDROP delete m_drag; #endif - -#if QT_CONFIG(xinput2) - finalizeXInput2(); -#endif - if (m_reader && m_reader->isRunning()) { sendConnectionEvent(QXcbAtom::_QT_CLOSE_CONNECTION); m_reader->wait(); @@ -757,58 +737,73 @@ break; } \ break; -//#define XCB_EVENT_DEBUG - -void printXcbEvent(const char *message, xcb_generic_event_t *event) -{ -#ifdef XCB_EVENT_DEBUG -#define PRINT_XCB_EVENT(ev) \ - case ev: \ - qDebug("QXcbConnection: %s: %d - %s - sequence: %d", message, int(ev), #ev, event->sequence); \ - break; - - switch (event->response_type & ~0x80) { - PRINT_XCB_EVENT(XCB_KEY_PRESS); - PRINT_XCB_EVENT(XCB_KEY_RELEASE); - PRINT_XCB_EVENT(XCB_BUTTON_PRESS); - PRINT_XCB_EVENT(XCB_BUTTON_RELEASE); - PRINT_XCB_EVENT(XCB_MOTION_NOTIFY); - PRINT_XCB_EVENT(XCB_ENTER_NOTIFY); - PRINT_XCB_EVENT(XCB_LEAVE_NOTIFY); - PRINT_XCB_EVENT(XCB_FOCUS_IN); - PRINT_XCB_EVENT(XCB_FOCUS_OUT); - PRINT_XCB_EVENT(XCB_KEYMAP_NOTIFY); - PRINT_XCB_EVENT(XCB_EXPOSE); - PRINT_XCB_EVENT(XCB_GRAPHICS_EXPOSURE); - PRINT_XCB_EVENT(XCB_NO_EXPOSURE); - PRINT_XCB_EVENT(XCB_VISIBILITY_NOTIFY); - PRINT_XCB_EVENT(XCB_CREATE_NOTIFY); - PRINT_XCB_EVENT(XCB_DESTROY_NOTIFY); - PRINT_XCB_EVENT(XCB_UNMAP_NOTIFY); - PRINT_XCB_EVENT(XCB_MAP_NOTIFY); - PRINT_XCB_EVENT(XCB_MAP_REQUEST); - PRINT_XCB_EVENT(XCB_REPARENT_NOTIFY); - PRINT_XCB_EVENT(XCB_CONFIGURE_NOTIFY); - PRINT_XCB_EVENT(XCB_CONFIGURE_REQUEST); - PRINT_XCB_EVENT(XCB_GRAVITY_NOTIFY); - PRINT_XCB_EVENT(XCB_RESIZE_REQUEST); - PRINT_XCB_EVENT(XCB_CIRCULATE_NOTIFY); - PRINT_XCB_EVENT(XCB_CIRCULATE_REQUEST); - PRINT_XCB_EVENT(XCB_PROPERTY_NOTIFY); - PRINT_XCB_EVENT(XCB_SELECTION_CLEAR); - PRINT_XCB_EVENT(XCB_SELECTION_REQUEST); - PRINT_XCB_EVENT(XCB_SELECTION_NOTIFY); - PRINT_XCB_EVENT(XCB_COLORMAP_NOTIFY); - PRINT_XCB_EVENT(XCB_CLIENT_MESSAGE); - PRINT_XCB_EVENT(XCB_MAPPING_NOTIFY); - PRINT_XCB_EVENT(XCB_GE_GENERIC); - default: - qDebug("QXcbConnection: %s: unknown event - response_type: %d - sequence: %d", message, int(event->response_type & ~0x80), int(event->sequence)); +void QXcbConnection::printXcbEvent(const QLoggingCategory &log, const char *message, + xcb_generic_event_t *event) const +{ + quint8 response_type = event->response_type & ~0x80; + quint16 sequence = event->sequence; + +#define PRINT_AND_RETURN(name) { \ + qCDebug(log, "%s | %s(%d) | sequence: %d", message, name, response_type, sequence); \ + return; \ +} +#define CASE_PRINT_AND_RETURN(name) case name : PRINT_AND_RETURN(#name); + + switch (response_type) { + CASE_PRINT_AND_RETURN( XCB_KEY_PRESS ); + CASE_PRINT_AND_RETURN( XCB_KEY_RELEASE ); + CASE_PRINT_AND_RETURN( XCB_BUTTON_PRESS ); + CASE_PRINT_AND_RETURN( XCB_BUTTON_RELEASE ); + CASE_PRINT_AND_RETURN( XCB_MOTION_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_ENTER_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_LEAVE_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_FOCUS_IN ); + CASE_PRINT_AND_RETURN( XCB_FOCUS_OUT ); + CASE_PRINT_AND_RETURN( XCB_KEYMAP_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_EXPOSE ); + CASE_PRINT_AND_RETURN( XCB_GRAPHICS_EXPOSURE ); + CASE_PRINT_AND_RETURN( XCB_NO_EXPOSURE ); + CASE_PRINT_AND_RETURN( XCB_VISIBILITY_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_CREATE_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_DESTROY_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_UNMAP_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_MAP_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_MAP_REQUEST ); + CASE_PRINT_AND_RETURN( XCB_REPARENT_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_CONFIGURE_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_CONFIGURE_REQUEST ); + CASE_PRINT_AND_RETURN( XCB_GRAVITY_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_RESIZE_REQUEST ); + CASE_PRINT_AND_RETURN( XCB_CIRCULATE_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_CIRCULATE_REQUEST ); + CASE_PRINT_AND_RETURN( XCB_PROPERTY_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_SELECTION_CLEAR ); + CASE_PRINT_AND_RETURN( XCB_SELECTION_REQUEST ); + CASE_PRINT_AND_RETURN( XCB_SELECTION_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_COLORMAP_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_CLIENT_MESSAGE ); + CASE_PRINT_AND_RETURN( XCB_MAPPING_NOTIFY ); + CASE_PRINT_AND_RETURN( XCB_GE_GENERIC ); } -#else - Q_UNUSED(message); - Q_UNUSED(event); -#endif + // XFixes + if (has_xfixes && response_type == xfixes_first_event + 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"); + } + // XKB + if (response_type == xkb_first_event) + PRINT_AND_RETURN("XCB_XKB_* event"); + + // UNKNOWN + qCDebug(log, "%s | unknown(%d) | sequence: %d", message, response_type, sequence); + +#undef PRINT_AND_RETURN +#undef CASE_PRINT_AND_RETURN } const char *xcb_errors[] = @@ -959,18 +954,6 @@ const char *xcb_protocol_request_codes[] = "Unknown" }; -#ifdef Q_XCB_DEBUG -void QXcbConnection::log(const char *file, int line, int sequence) -{ - QMutexLocker locker(&m_callLogMutex); - CallInfo info; - info.sequence = sequence; - info.file = file; - info.line = line; - m_callLog << info; -} -#endif - void QXcbConnection::handleXcbError(xcb_generic_error_t *error) { long result = 0; @@ -986,26 +969,6 @@ void QXcbConnection::handleXcbError(xcb_generic_error_t *error) int(error->sequence), int(error->resource_id), int(error->major_code), xcb_protocol_request_codes[clamped_major_code], int(error->minor_code)); -#ifdef Q_XCB_DEBUG - QMutexLocker locker(&m_callLogMutex); - int i = 0; - for (; i < m_callLog.size(); ++i) { - if (m_callLog.at(i).sequence == error->sequence) { - qDebug("Caused by: %s:%d", m_callLog.at(i).file.constData(), m_callLog.at(i).line); - break; - } else if (m_callLog.at(i).sequence > error->sequence) { - qDebug("Caused some time before: %s:%d", m_callLog.at(i).file.constData(), - m_callLog.at(i).line); - if (i > 0) - qDebug("and after: %s:%d", m_callLog.at(i-1).file.constData(), - m_callLog.at(i-1).line); - break; - } - } - if (i == m_callLog.size() && !m_callLog.isEmpty()) - qDebug("Caused some time after: %s:%d", qAsConst(m_callLog).first().file.constData(), - qAsConst(m_callLog).first().line); -#endif } static Qt::MouseButtons translateMouseButtons(int s) @@ -1075,17 +1038,6 @@ namespace { void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) { -#ifdef Q_XCB_DEBUG - { - QMutexLocker locker(&m_callLogMutex); - int i = 0; - for (; i < m_callLog.size(); ++i) - if (m_callLog.at(i).sequence >= event->sequence) - break; - m_callLog.remove(0, i); - } -#endif - long result = 0; QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(); bool handled = dispatcher && dispatcher->filterNativeEvent(m_nativeInterface->genericEventFilterType(), event, &result); @@ -1097,34 +1049,33 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) case XCB_EXPOSE: HANDLE_PLATFORM_WINDOW_EVENT(xcb_expose_event_t, window, handleExposeEvent); - // press/release/motion is only delivered here when XI 2.2+ is _not_ in use case XCB_BUTTON_PRESS: { xcb_button_press_event_t *ev = (xcb_button_press_event_t *)event; m_keyboard->updateXKBStateFromCore(ev->state); // the event explicitly contains the state of the three first buttons, // the rest we need to manage ourselves - m_buttons = (m_buttons & ~0x7) | translateMouseButtons(ev->state); - m_buttons |= translateMouseButton(ev->detail); + m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state); + m_buttonState |= translateMouseButton(ev->detail); if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) - qCDebug(lcQpaXInputEvents, "legacy mouse press, button %d state %X", ev->detail, static_cast<unsigned int>(m_buttons)); + qCDebug(lcQpaXInputEvents, "legacy mouse press, button %d state %X", ev->detail, static_cast<unsigned int>(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent); } case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *ev = (xcb_button_release_event_t *)event; m_keyboard->updateXKBStateFromCore(ev->state); - m_buttons = (m_buttons & ~0x7) | translateMouseButtons(ev->state); - m_buttons &= ~translateMouseButton(ev->detail); + m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state); + m_buttonState &= ~translateMouseButton(ev->detail); if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) - qCDebug(lcQpaXInputEvents, "legacy mouse release, button %d state %X", ev->detail, static_cast<unsigned int>(m_buttons)); + qCDebug(lcQpaXInputEvents, "legacy mouse release, button %d state %X", ev->detail, static_cast<unsigned int>(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent); } case XCB_MOTION_NOTIFY: { xcb_motion_notify_event_t *ev = (xcb_motion_notify_event_t *)event; m_keyboard->updateXKBStateFromCore(ev->state); - m_buttons = (m_buttons & ~0x7) | translateMouseButtons(ev->state); + m_buttonState = (m_buttonState & ~0x7) | translateMouseButtons(ev->state); if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "legacy mouse move %d,%d button %d state %X", ev->event_x, ev->event_y, - ev->detail, static_cast<unsigned int>(m_buttons)); + ev->detail, static_cast<unsigned int>(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent); } @@ -1140,14 +1091,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) handleClientMessageEvent((xcb_client_message_event_t *)event); break; case XCB_ENTER_NOTIFY: -#ifdef XCB_USE_XINPUT22 - if (isAtLeastXI22() && xi2MouseEvents()) +#if QT_CONFIG(xinput2) + if (hasXInput2() && !xi2MouseEventsDisabled()) break; #endif HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent); case XCB_LEAVE_NOTIFY: -#ifdef XCB_USE_XINPUT22 - if (isAtLeastXI22() && xi2MouseEvents()) +#if QT_CONFIG(xinput2) + if (hasXInput2() && !xi2MouseEventsDisabled()) break; #endif m_keyboard->updateXKBStateFromCore(((xcb_leave_notify_event_t *)event)->state); @@ -1212,7 +1163,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) #if QT_CONFIG(xinput2) case XCB_GE_GENERIC: // Here the windowEventListener is invoked from xi2HandleEvent() - if (m_xi2Enabled && isXIEvent(event, m_xiOpCode)) + if (hasXInput2() && isXIEvent(event, m_xiOpCode)) xi2HandleEvent(reinterpret_cast<xcb_ge_event_t *>(event)); break; #endif @@ -1223,7 +1174,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) } if (!handled) { - if (response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) { + if (has_xfixes && response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) { xcb_xfixes_selection_notify_event_t *notify_event = reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event); setTime(notify_event->timestamp); #ifndef QT_NO_CLIPBOARD @@ -1275,10 +1226,10 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) if (!handled && m_glIntegration) handled = m_glIntegration->handleXcbEvent(event, response_type); - if (handled) - printXcbEvent("Handled XCB event", event); - else - printXcbEvent("Unhandled XCB event", event); +#if 0 + if (Q_UNLIKELY(lcQpaEvents().isDebugEnabled())) + printXcbEvent(lcQpaEvents(), handled ? "Handled" : "Unhandled", event); +#endif } void QXcbConnection::addPeekFunc(PeekFunc f) @@ -1286,6 +1237,95 @@ 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) { @@ -1400,10 +1440,10 @@ void QXcbConnection::sendConnectionEvent(QXcbAtom::Atom a, uint id) 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; - Q_XCB_CALL(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)); + 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; @@ -1412,8 +1452,8 @@ void QXcbConnection::sendConnectionEvent(QXcbAtom::Atom a, uint id) event.type = atom(a); event.data.data32[0] = id; - Q_XCB_CALL(xcb_send_event(xcb_connection(), false, eventListener, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event))); - Q_XCB_CALL(xcb_destroy_window(m_connection, eventListener)); + 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()); } @@ -1471,13 +1511,7 @@ xcb_timestamp_t QXcbConnection::getTimestamp() xcb_window_t QXcbConnection::getSelectionOwner(xcb_atom_t atom) const { - xcb_connection_t *c = xcb_connection(); - xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom); - xcb_get_selection_owner_reply_t *reply; - reply = xcb_get_selection_owner_reply(c, cookie, 0); - xcb_window_t win = reply->owner; - free(reply); - return win; + return Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom)->owner; } xcb_window_t QXcbConnection::getQtSelectionOwner() @@ -1487,16 +1521,16 @@ xcb_window_t QXcbConnection::getQtSelectionOwner() int16_t x = 0, y = 0; uint16_t w = 3, h = 3; m_qtSelectionOwner = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_window(xcb_connection(), - XCB_COPY_FROM_PARENT, // depth -- same as root - m_qtSelectionOwner, // window id - xcbScreen->root, // parent window id - x, y, w, h, - 0, // border width - XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class - xcbScreen->root_visual, // visual - 0, // value mask - 0)); // value list + xcb_create_window(xcb_connection(), + XCB_COPY_FROM_PARENT, // depth -- same as root + m_qtSelectionOwner, // window id + xcbScreen->root, // parent window id + x, y, w, h, + 0, // border width + XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class + xcbScreen->root_visual, // visual + 0, // value mask + 0); // value list } return m_qtSelectionOwner; } @@ -1512,47 +1546,47 @@ xcb_window_t QXcbConnection::clientLeader() if (m_clientLeader == 0) { m_clientLeader = xcb_generate_id(xcb_connection()); QXcbScreen *screen = primaryScreen(); - Q_XCB_CALL(xcb_create_window(xcb_connection(), - XCB_COPY_FROM_PARENT, - m_clientLeader, - screen->root(), - 0, 0, 1, 1, - 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - screen->screen()->root_visual, - 0, 0)); + xcb_create_window(xcb_connection(), + XCB_COPY_FROM_PARENT, + m_clientLeader, + screen->root(), + 0, 0, 1, 1, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + screen->screen()->root_visual, + 0, 0); #ifndef QT_NO_DEBUG QByteArray ba("Qt client leader window"); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_clientLeader, - atom(QXcbAtom::_NET_WM_NAME), - atom(QXcbAtom::UTF8_STRING), - 8, - ba.length(), - ba.constData())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_clientLeader, + atom(QXcbAtom::_NET_WM_NAME), + atom(QXcbAtom::UTF8_STRING), + 8, + ba.length(), + ba.constData()); #endif - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_clientLeader, - atom(QXcbAtom::WM_CLIENT_LEADER), - XCB_ATOM_WINDOW, - 32, - 1, - &m_clientLeader)); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_clientLeader, + atom(QXcbAtom::WM_CLIENT_LEADER), + XCB_ATOM_WINDOW, + 32, + 1, + &m_clientLeader); #if QT_CONFIG(xcb_sm) // If we are session managed, inform the window manager about it QByteArray session = qGuiApp->sessionId().toLatin1(); if (!session.isEmpty()) { - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_clientLeader, - atom(QXcbAtom::SM_CLIENT_ID), - XCB_ATOM_STRING, - 8, - session.length(), - session.constData())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_clientLeader, + atom(QXcbAtom::SM_CLIENT_ID), + XCB_ATOM_STRING, + 8, + session.length(), + session.constData()); } #endif } @@ -1574,7 +1608,8 @@ void *QXcbConnection::createVisualInfoForDefaultVisualId() const info.visualid = m_defaultVisualId; int count = 0; - XVisualInfo *retVisual = XGetVisualInfo(DISPLAY_FROM_XCB(this), VisualIDMask, &info, &count); + Display *dpy = static_cast<Display *>(connection()->xlib_display()); + XVisualInfo *retVisual = XGetVisualInfo(dpy, VisualIDMask, &info, &count); Q_ASSERT(count < 2); return retVisual; } @@ -1629,14 +1664,15 @@ bool QXcbConnection::compressEvent(xcb_generic_event_t *event, int currentIndex, #if QT_CONFIG(xinput2) // compress XI_* events if (responseType == XCB_GE_GENERIC) { - if (!m_xi2Enabled) + if (!hasXInput2()) return false; // compress XI_Motion, but not from tablet devices if (isXIType(event, m_xiOpCode, XI_Motion)) { #if QT_CONFIG(tabletevent) xXIDeviceEvent *xdev = reinterpret_cast<xXIDeviceEvent *>(event); - if (const_cast<QXcbConnection *>(this)->tabletDataForDevice(xdev->sourceid)) + 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) { @@ -1736,6 +1772,8 @@ void QXcbConnection::processXcbEvents() m_reader->unlock(); + m_peekerIndexCacheDirty = m_mainEventLoopFlushedQueue = true; + // Indicate with a null event that the event the callbacks are waiting for // is not in the queue currently. for (PeekFunc f : qAsConst(m_peekFuncs)) @@ -2003,11 +2041,7 @@ xcb_atom_t QXcbConnection::internAtom(const char *name) if (!name || *name == 0) return XCB_NONE; - xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xcb_connection(), false, strlen(name), name); - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_connection(), cookie, 0); - int atom = reply->atom; - free(reply); - return atom; + return Q_XCB_REPLY(xcb_intern_atom, xcb_connection(), false, strlen(name), name)->atom; } QByteArray QXcbConnection::atomName(xcb_atom_t atom) @@ -2015,18 +2049,12 @@ QByteArray QXcbConnection::atomName(xcb_atom_t atom) if (!atom) return QByteArray(); - xcb_generic_error_t *error = 0; - xcb_get_atom_name_cookie_t cookie = Q_XCB_CALL(xcb_get_atom_name(xcb_connection(), atom)); - xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(xcb_connection(), cookie, &error); - if (error) { + auto reply = Q_XCB_REPLY(xcb_get_atom_name, xcb_connection(), atom); + if (!reply) qWarning() << "QXcbConnection::atomName: bad Atom" << atom; - free(error); - } - if (reply) { - QByteArray result(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)); - free(reply); - return result; - } + else + return QByteArray(xcb_get_atom_name_name(reply.get()), xcb_get_atom_name_name_length(reply.get())); + return QByteArray(); } @@ -2048,29 +2076,25 @@ const xcb_format_t *QXcbConnection::formatForDepth(uint8_t depth) const void QXcbConnection::sync() { // from xcb_aux_sync - xcb_get_input_focus_cookie_t cookie = Q_XCB_CALL(xcb_get_input_focus(xcb_connection())); + xcb_get_input_focus_cookie_t cookie = xcb_get_input_focus(xcb_connection()); free(xcb_get_input_focus_reply(xcb_connection(), cookie, 0)); } void QXcbConnection::initializeXFixes() { - xcb_generic_error_t *error = 0; const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_xfixes_id); if (!reply || !reply->present) return; - xfixes_first_event = reply->first_event; - xcb_xfixes_query_version_cookie_t xfixes_query_cookie = xcb_xfixes_query_version(m_connection, - XCB_XFIXES_MAJOR_VERSION, - XCB_XFIXES_MINOR_VERSION); - xcb_xfixes_query_version_reply_t *xfixes_query = xcb_xfixes_query_version_reply (m_connection, - xfixes_query_cookie, &error); - if (!xfixes_query || error || xfixes_query->major_version < 2) { + 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"); - free(error); - xfixes_first_event = 0; + return; } - free(xfixes_query); + xfixes_first_event = reply->first_event; + has_xfixes = true; } void QXcbConnection::initializeXRender() @@ -2080,17 +2104,14 @@ void QXcbConnection::initializeXRender() if (!reply || !reply->present) return; - xcb_generic_error_t *error = 0; - xcb_render_query_version_cookie_t xrender_query_cookie = xcb_render_query_version(m_connection, - XCB_RENDER_MAJOR_VERSION, - XCB_RENDER_MINOR_VERSION); - xcb_render_query_version_reply_t *xrender_query = xcb_render_query_version_reply(m_connection, - xrender_query_cookie, &error); - if (!xrender_query || error || (xrender_query->major_version == 0 && xrender_query->minor_version < 5)) { + auto xrender_query = Q_XCB_REPLY(xcb_render_query_version, m_connection, + XCB_RENDER_MAJOR_VERSION, + XCB_RENDER_MINOR_VERSION); + if (!xrender_query || (xrender_query->major_version == 0 && xrender_query->minor_version < 5)) { qWarning("QXcbConnection: Failed to initialize XRender"); - free(error); + return; } - free(xrender_query); + has_render_extension = true; #endif } @@ -2102,21 +2123,16 @@ void QXcbConnection::initializeXRandr() xrandr_first_event = reply->first_event; - xcb_generic_error_t *error = 0; - xcb_randr_query_version_cookie_t xrandr_query_cookie = xcb_randr_query_version(m_connection, - XCB_RANDR_MAJOR_VERSION, - XCB_RANDR_MINOR_VERSION); + auto xrandr_query = Q_XCB_REPLY(xcb_randr_query_version, m_connection, + XCB_RANDR_MAJOR_VERSION, + XCB_RANDR_MINOR_VERSION); has_randr_extension = true; - xcb_randr_query_version_reply_t *xrandr_query = xcb_randr_query_version_reply(m_connection, - xrandr_query_cookie, &error); - if (!xrandr_query || error || (xrandr_query->major_version < 1 || (xrandr_query->major_version == 1 && xrandr_query->minor_version < 2))) { + if (!xrandr_query || (xrandr_query->major_version < 1 || (xrandr_query->major_version == 1 && xrandr_query->minor_version < 2))) { qWarning("QXcbConnection: Failed to initialize XRandr"); - free(error); has_randr_extension = false; } - free(xrandr_query); xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(m_setup); for (; rootIter.rem; xcb_screen_next(&rootIter)) { @@ -2136,14 +2152,8 @@ void QXcbConnection::initializeXinerama() if (!reply || !reply->present) return; - xcb_generic_error_t *error = Q_NULLPTR; - xcb_xinerama_is_active_cookie_t xinerama_query_cookie = xcb_xinerama_is_active(m_connection); - xcb_xinerama_is_active_reply_t *xinerama_is_active = xcb_xinerama_is_active_reply(m_connection, - xinerama_query_cookie, - &error); - has_xinerama_extension = xinerama_is_active && !error && xinerama_is_active->state; - free(error); - free(xinerama_is_active); + 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() @@ -2153,16 +2163,13 @@ void QXcbConnection::initializeXShape() return; has_shape_extension = true; - xcb_shape_query_version_cookie_t cookie = xcb_shape_query_version(m_connection); - xcb_shape_query_version_reply_t *shape_query = xcb_shape_query_version_reply(m_connection, - cookie, NULL); + 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; } - free(shape_query); } void QXcbConnection::initializeXKB() @@ -2177,11 +2184,10 @@ void QXcbConnection::initializeXKB() xkb_first_event = reply->first_event; xcb_connection_t *c = connection()->xcb_connection(); - xcb_xkb_use_extension_cookie_t xkb_query_cookie; - xcb_xkb_use_extension_reply_t *xkb_query; - xkb_query_cookie = xcb_xkb_use_extension(c, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION); - xkb_query = xcb_xkb_use_extension_reply(c, xkb_query_cookie, 0); + 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"); @@ -2190,12 +2196,10 @@ void QXcbConnection::initializeXKB() 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); - free(xkb_query); return; } has_xkb = true; - free(xkb_query); const uint16_t required_map_parts = (XCB_XKB_MAP_PART_KEY_TYPES | XCB_XKB_MAP_PART_KEY_SYMS | @@ -2230,62 +2234,6 @@ void QXcbConnection::initializeXKB() #endif } -#if defined(XCB_USE_XINPUT22) -bool QXcbConnection::xi2MouseEvents() const -{ - static bool mouseViaXI2 = !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 mouseViaXI2 && !has_xinerama_extension; -} -#endif - -#if QT_CONFIG(xinput2) -static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number) -{ - int offset = 0; - for (int i = 0; i < maskLen; i++) { - if (number < 8) { - if ((maskPtr[i] & (1 << number)) == 0) - return -1; - } - for (int j = 0; j < 8; j++) { - if (j == number) - return offset; - if (maskPtr[i] & (1 << j)) - offset++; - } - number -= 8; - } - return -1; -} - -bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value) -{ - const xXIDeviceEvent *xideviceevent = static_cast<const xXIDeviceEvent *>(event); - const unsigned char *buttonsMaskAddr = (const unsigned char*)&xideviceevent[1]; - const unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4; - FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4); - - int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum); - if (valuatorOffset < 0) - return false; - - *value = valuatorsValuesAddr[valuatorOffset].integral; - *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16)); - return true; -} - -void QXcbConnection::xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event) -{ - // xcb event structs contain stuff that wasn't on the wire, the full_sequence field - // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. - // Move this data back to have the same layout in memory as it was on the wire - // and allow casting, overwriting the full_sequence field. - memmove((char*) event + 32, (char*) event + 36, event->length * 4); -} -#endif // QT_CONFIG(xinput2) - QXcbSystemTrayTracker *QXcbConnection::systemTrayTracker() const { if (!m_systemTrayTracker) { diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index edbc8d846e..999dc0630c 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -56,6 +56,9 @@ #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) @@ -76,19 +79,19 @@ #define XCB_USE_XINPUT22 // XI 2.2 adds multi-point touch support #endif #endif -struct XInput2TouchDeviceData; #endif // QT_CONFIG(xinput2) struct xcb_randr_get_output_info_reply_t; -//#define Q_XCB_DEBUG - QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQpaXInput) 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) class QXcbVirtualDesktop; class QXcbScreen; @@ -358,7 +361,7 @@ public: virtual void handleFocusInEvent(const xcb_focus_in_event_t *) {} virtual void handleFocusOutEvent(const xcb_focus_out_event_t *) {} virtual void handlePropertyNotifyEvent(const xcb_property_notify_event_t *) {} -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) virtual void handleXIMouseEvent(xcb_ge_event_t *, Qt::MouseEventSource = Qt::MouseEventNotSynthesized) {} virtual void handleXIEnterLeave(xcb_ge_event_t *) {} #endif @@ -426,26 +429,12 @@ public: void *xlib_display() const; void *createVisualInfoForDefaultVisualId() const; #endif - -#if QT_CONFIG(xinput2) - void xi2Select(xcb_window_t window); - void xi2SelectStateEvents(); -#endif -#ifdef XCB_USE_XINPUT21 - bool isAtLeastXI21() const { return m_xi2Enabled && m_xi2Minor >= 1; } -#else - bool isAtLeastXI21() const { return false; } -#endif -#ifdef XCB_USE_XINPUT22 - bool isAtLeastXI22() const { return m_xi2Enabled && m_xi2Minor >= 2; } -#else - bool isAtLeastXI22() const { return false; } -#endif - void sync(); void handleXcbError(xcb_generic_error_t *error); void handleXcbEvent(xcb_generic_event_t *event); + void printXcbEvent(const QLoggingCategory &log, const char *message, + xcb_generic_event_t *event) const; void addWindowEventListener(xcb_window_t id, QXcbWindowEventListener *eventListener); void removeWindowEventListener(xcb_window_t id); @@ -458,27 +447,37 @@ public: 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 xfixes_first_event > 0; } + 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() const { return has_render_extension; } + bool hasXInput2() const { return m_xi2Enabled; } - bool supportsThreadedRendering() const { return m_reader->isRunning(); } bool threadedEventHandling() const { return m_reader->isRunning(); } xcb_timestamp_t getTimestamp(); xcb_window_t getSelectionOwner(xcb_atom_t atom) const; xcb_window_t getQtSelectionOwner(); - void setButton(Qt::MouseButton button, bool down) { m_buttons.setFlag(button, down); } - Qt::MouseButtons buttons() const { return m_buttons; } + void setButtonState(Qt::MouseButton button, bool down) { m_buttonState.setFlag(button, down); } + Qt::MouseButtons buttonState() const { return m_buttonState; } Qt::MouseButton translateMouseButton(xcb_button_t s); QXcbWindow *focusWindow() const { return m_focusWindow; } @@ -501,27 +500,29 @@ public: static bool xEmbedSystemTrayAvailable(); static bool xEmbedSystemTrayVisualHasAlphaChannel(); +#if QT_CONFIG(xinput2) + 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); #ifdef XCB_USE_XINPUT21 - void handleEnterEvent(); + void xi2UpdateScrollingDevices(); #endif - #ifdef XCB_USE_XINPUT22 bool startSystemResizeForTouchBegin(xcb_window_t window, const QPoint &point, Qt::Corner corner); - bool xi2SetMouseGrabEnabled(xcb_window_t w, bool grab); + bool isTouchScreen(int id); +#endif #endif - Qt::MouseButton xiToQtMouseButton(uint32_t b); - QXcbEventReader *eventReader() const { return m_reader; } bool canGrab() const { return m_canGrabServer; } QXcbGlIntegration *glIntegration() const { return m_glIntegration; } -#ifdef XCB_USE_XINPUT22 - bool xi2MouseEvents() const; - bool isTouchScreen(int id) const; -#endif - protected: bool event(QEvent *e) override; @@ -553,15 +554,35 @@ private: void destroyScreen(QXcbScreen *screen); void initializeScreens(); bool compressEvent(xcb_generic_event_t *event, int currentIndex, QXcbEventArray *eventqueue) const; -#if QT_CONFIG(xinput2) + bool m_xi2Enabled = false; - int m_xi2Minor = 2; +#if QT_CONFIG(xinput2) + int m_xi2Minor = -1; void initializeXInput2(); - void finalizeXInput2(); + void xi2SetupDevice(void *info, bool removeExisting = true); void xi2SetupDevices(); - XInput2TouchDeviceData *touchDeviceForId(int id); + struct TouchDeviceData { + QTouchDevice *qtTouchDevice = nullptr; + QHash<int, QWindowSystemInterface::TouchPoint> touchPoints; + QHash<int, QPointF> pointPressedPosition; // in screen coordinates where each point was pressed + struct ValuatorClassInfo { + double min = 0; + double max = 0; + int number = -1; + QXcbAtom::Atom label; + }; + QVector<ValuatorClassInfo> valuatorInfo; + + // Stuff that is relevant only for touchpads + QPointF firstPressedPosition; // in screen coordinates where the first point was pressed + QPointF firstPressedNormalPosition; // device coordinates (0 to 1, 0 to 1) where the first point was pressed + QSizeF size; // device size in mm + bool providesTouchOrientation = false; + }; + TouchDeviceData *populateTouchDevices(void *info); + TouchDeviceData *touchDeviceForId(int id); void xi2HandleEvent(xcb_ge_event_t *event); - void xi2HandleHierachyEvent(void *event); + void xi2HandleHierarchyEvent(void *event); void xi2HandleDeviceChangedEvent(void *event); int m_xiOpCode, m_xiEventBase, m_xiErrorBase; #ifdef XCB_USE_XINPUT22 @@ -600,9 +621,12 @@ private: Qt::Orientations legacyOrientations = 0; QPointF lastScrollPosition; }; - void updateScrollingDevice(ScrollingDevice& scrollingDevice, int num_classes, void *classes); - void xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice); QHash<int, ScrollingDevice> m_scrollingDevices; +#ifdef XCB_USE_XINPUT21 + void xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice); + void xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice); + ScrollingDevice *scrollingDeviceForId(int id); +#endif static bool xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value); static void xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event); @@ -638,34 +662,18 @@ private: void *m_xlib_display = nullptr; #endif QXcbEventReader *m_reader = nullptr; + #if QT_CONFIG(xinput2) - QHash<int, XInput2TouchDeviceData*> m_touchDevices; + QHash<int, TouchDeviceData> m_touchDevices; #ifdef XCB_USE_XINPUT22 struct StartSystemResizeInfo { - xcb_window_t window; + xcb_window_t window = XCB_NONE; uint16_t deviceid; uint32_t pointid; Qt::Corner corner; } m_startSystemResizeInfo; #endif #endif -#ifdef Q_XCB_DEBUG - struct CallInfo { - int sequence; - QByteArray file; - int line; - }; - QVector<CallInfo> m_callLog; - QMutex m_callLogMutex; - void log(const char *file, int line, int sequence); - template <typename cookie_t> - friend cookie_t q_xcb_call_template(const cookie_t &cookie, QXcbConnection *connection, - const char *file, int line); - template <typename reply_t> - friend reply_t *q_xcb_call_template(reply_t *reply, QXcbConnection *connection, - const char *file, int line); -#endif - WindowMapper m_mapper; QVector<PeekFunc> m_peekFuncs; @@ -674,13 +682,15 @@ private: uint32_t xrandr_first_event = 0; uint32_t xkb_first_event = 0; + 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; - Qt::MouseButtons m_buttons = 0; + Qt::MouseButtons m_buttonState = 0; QXcbWindow *m_focusWindow = nullptr; QXcbWindow *m_mouseGrabber = nullptr; @@ -694,6 +704,10 @@ 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; }; #if QT_CONFIG(xinput2) @@ -703,9 +717,6 @@ Q_DECLARE_TYPEINFO(QXcbConnection::TabletData, Q_MOVABLE_TYPE); #endif #endif -#define DISPLAY_FROM_XCB(object) (reinterpret_cast<Display *>(object->connection()->xlib_display())) -#define CREATE_VISUALINFO_FROM_DEFAULT_VISUALID(object) ((XVisualInfo *)(object->connection()->createVisualInfoForDefaultVisualId())) - template<typename T> xcb_generic_event_t *QXcbConnection::checkEvent(T &checker) { @@ -733,6 +744,20 @@ private: QXcbConnection *m_connection; }; +#define Q_XCB_REPLY_CONNECTION_ARG(connection, ...) connection + +#define Q_XCB_REPLY(call, ...) \ + std::unique_ptr<call##_reply_t, decltype(std::free) *>( \ + call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call(__VA_ARGS__), nullptr), \ + std::free \ + ) + +#define Q_XCB_REPLY_UNCHECKED(call, ...) \ + std::unique_ptr<call##_reply_t, decltype(std::free) *>( \ + call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call##_unchecked(__VA_ARGS__), nullptr), \ + std::free \ + ) + template <typename T> union q_padded_xcb_event { T event; @@ -746,30 +771,6 @@ union q_padded_xcb_event { q_padded_xcb_event<event_type> store = {}; \ auto &event_var = store.event; -#ifdef Q_XCB_DEBUG -template <typename cookie_t> -cookie_t q_xcb_call_template(const cookie_t &cookie, QXcbConnection *connection, const char *file, - int line) -{ - connection->log(file, line, cookie.sequence); - return cookie; -} - -template <typename reply_t> -reply_t *q_xcb_call_template(reply_t *reply, QXcbConnection *connection, const char *file, int line) -{ - connection->log(file, line, reply->sequence); - return reply; -} -#define Q_XCB_CALL(x) q_xcb_call_template(x, connection(), __FILE__, __LINE__) -#define Q_XCB_CALL2(x, connection) q_xcb_call_template(x, connection, __FILE__, __LINE__) -#define Q_XCB_NOOP(c) q_xcb_call_template(xcb_no_operation(c->xcb_connection()), c, __FILE__, __LINE__); -#else -#define Q_XCB_CALL(x) x -#define Q_XCB_CALL2(x, connection) x -#define Q_XCB_NOOP(c) (void)c; -#endif - QT_END_NAMESPACE #endif diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index d1d97affe8..b2dcf7c3e7 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -50,19 +50,6 @@ #include <X11/extensions/XInput2.h> #include <X11/extensions/XI2proto.h> -struct XInput2TouchDeviceData { - XIDeviceInfo *xiDeviceInfo = nullptr; - QTouchDevice *qtTouchDevice = nullptr; - QHash<int, QWindowSystemInterface::TouchPoint> touchPoints; - QHash<int, QPointF> pointPressedPosition; // in screen coordinates where each point was pressed - - // Stuff that is relevant only for touchpads - QPointF firstPressedPosition; // in screen coordinates where the first point was pressed - QPointF firstPressedNormalPosition; // device coordinates (0 to 1, 0 to 1) where the first point was pressed - QSizeF size; // device size in mm - bool providesTouchOrientation = false; -}; - void QXcbConnection::initializeXInput2() { // TODO Qt 6 (or perhaps earlier): remove these redundant env variables @@ -70,30 +57,37 @@ void QXcbConnection::initializeXInput2() const_cast<QLoggingCategory&>(lcQpaXInput()).setEnabled(QtDebugMsg, true); if (qEnvironmentVariableIsSet("QT_XCB_DEBUG_XINPUT_DEVICES")) const_cast<QLoggingCategory&>(lcQpaXInputDevices()).setEnabled(QtDebugMsg, true); + Display *xDisplay = static_cast<Display *>(m_xlib_display); if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) { int xiMajor = 2; - // try 2.2 first, needed for TouchBegin/Update/End - if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { - m_xi2Minor = 1; // for smooth scrolling 2.1 is enough - if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { - m_xi2Minor = 0; // for tablet support 2.0 is enough - m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest; - } else - m_xi2Enabled = true; - } else - m_xi2Enabled = true; - if (m_xi2Enabled) { -#ifdef XCB_USE_XINPUT22 - qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.2 or greater", xiMajor, m_xi2Minor); - m_startSystemResizeInfo.window = XCB_NONE; +#if defined(XCB_USE_XINPUT22) + m_xi2Minor = 2; // for touch support 2.2 is enough +#elif defined(XCB_USE_XINPUT21) + m_xi2Minor = 1; // for smooth scrolling 2.1 is enough #else - qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.0", xiMajor, m_xi2Minor); + m_xi2Minor = 0; // for tablet support 2.0 is enough #endif + qCDebug(lcQpaXInput, "Plugin build with support for XInput 2 version up " + "to %d.%d", xiMajor, m_xi2Minor); + + switch (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor)) { + case Success: + // Server's supported version can be lower than the version we have + // announced to support. In this case Qt client will be limited by + // X server's supported version. + qCDebug(lcQpaXInput, "Using XInput version %d.%d", xiMajor, m_xi2Minor); + m_xi2Enabled = true; + xi2SetupDevices(); xi2SelectStateEvents(); + break; + case BadRequest: // Must be an X server with XInput 1 + qCDebug(lcQpaXInput, "X server does not support XInput 2"); + break; + default: // BadValue + qCDebug(lcQpaXInput, "Internal error"); + break; } - - xi2SetupDevices(); } } @@ -106,6 +100,7 @@ void QXcbConnection::xi2SelectStateEvents() XIEventMask xiEventMask; bitMask = XI_HierarchyChangedMask; bitMask |= XI_DeviceChangedMask; + bitMask |= XI_PropertyEventMask; xiEventMask.deviceid = XIAllDevices; xiEventMask.mask_len = sizeof(bitMask); xiEventMask.mask = xiBitMask; @@ -113,377 +108,439 @@ void QXcbConnection::xi2SelectStateEvents() XISelectEvents(dpy, DefaultRootWindow(dpy), &xiEventMask, 1); } -void QXcbConnection::xi2SetupDevices() +void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window) { -#if QT_CONFIG(tabletevent) - m_tabletData.clear(); + if (window == rootWindow()) + return; + + unsigned int bitMask = 0; + unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask); + bitMask |= XI_ButtonPressMask; + bitMask |= XI_ButtonReleaseMask; + bitMask |= XI_MotionMask; + // There is a check for enter/leave events in plain xcb enter/leave event handler, + // core enter/leave events will be ignored in this case. + bitMask |= XI_EnterMask; + bitMask |= XI_LeaveMask; +#ifdef XCB_USE_XINPUT22 + if (isAtLeastXI22()) { + bitMask |= XI_TouchBeginMask; + bitMask |= XI_TouchUpdateMask; + bitMask |= XI_TouchEndMask; + } #endif - m_scrollingDevices.clear(); - if (!m_xi2Enabled) - return; + XIEventMask mask; + mask.mask_len = sizeof(bitMask); + mask.mask = xiBitMask; + mask.deviceid = XIAllMasterDevices; + Display *dpy = static_cast<Display *>(m_xlib_display); + Status result = XISelectEvents(dpy, window, &mask, 1); + if (result == Success) + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + else + qCDebug(lcQpaXInput, "failed to select events, window %x, result %d", window, result); +} - Display *xDisplay = static_cast<Display *>(m_xlib_display); - int deviceCount = 0; - XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount); - for (int i = 0; i < deviceCount; ++i) { - // Only non-master pointing devices are relevant here. - if (devices[i].use != XISlavePointer) - continue; - qCDebug(lcQpaXInputDevices) << "input device " << devices[i].name << "ID" << devices[i].deviceid; +void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting) +{ + XIDeviceInfo *deviceInfo = reinterpret_cast<XIDeviceInfo *>(info); + if (removeExisting) { #if QT_CONFIG(tabletevent) - TabletData tabletData; + for (int i = 0; i < m_tabletData.count(); ++i) { + if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) { + m_tabletData.remove(i); + break; + } + } #endif - ScrollingDevice scrollingDevice; - for (int c = 0; c < devices[i].num_classes; ++c) { - switch (devices[i].classes[c]->type) { - case XIValuatorClass: { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(devices[i].classes[c]); - const int valuatorAtom = qatom(vci->label); - qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms); + m_scrollingDevices.remove(deviceInfo->deviceid); + m_touchDevices.remove(deviceInfo->deviceid); + } + + qCDebug(lcQpaXInputDevices) << "input device " << deviceInfo->name << "ID" << deviceInfo->deviceid; #if QT_CONFIG(tabletevent) - if (valuatorAtom < QXcbAtom::NAtoms) { - TabletData::ValuatorClassInfo info; - info.minVal = vci->min; - info.maxVal = vci->max; - info.number = vci->number; - tabletData.valuatorInfo[valuatorAtom] = info; - } -#endif // QT_CONFIG(tabletevent) - if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) - scrollingDevice.lastScrollPosition.setX(vci->value); - else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) - scrollingDevice.lastScrollPosition.setY(vci->value); - break; + TabletData tabletData; +#endif + ScrollingDevice scrollingDevice; + for (int c = 0; c < deviceInfo->num_classes; ++c) { + XIAnyClassInfo *classinfo = deviceInfo->classes[c]; + switch (classinfo->type) { + case XIValuatorClass: { + XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); + const int valuatorAtom = qatom(vci->label); + qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms); +#if QT_CONFIG(tabletevent) + if (valuatorAtom < QXcbAtom::NAtoms) { + TabletData::ValuatorClassInfo info; + info.minVal = vci->min; + info.maxVal = vci->max; + info.number = vci->number; + tabletData.valuatorInfo[valuatorAtom] = info; } +#endif // QT_CONFIG(tabletevent) + if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) + scrollingDevice.lastScrollPosition.setX(vci->value); + else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) + scrollingDevice.lastScrollPosition.setY(vci->value); + break; + } #ifdef XCB_USE_XINPUT21 - case XIScrollClass: { - XIScrollClassInfo *sci = reinterpret_cast<XIScrollClassInfo *>(devices[i].classes[c]); - if (sci->scroll_type == XIScrollTypeVertical) { - scrollingDevice.orientations |= Qt::Vertical; - scrollingDevice.verticalIndex = sci->number; - scrollingDevice.verticalIncrement = sci->increment; - } - else if (sci->scroll_type == XIScrollTypeHorizontal) { - scrollingDevice.orientations |= Qt::Horizontal; - scrollingDevice.horizontalIndex = sci->number; - scrollingDevice.horizontalIncrement = sci->increment; - } - break; + case XIScrollClass: { + XIScrollClassInfo *sci = reinterpret_cast<XIScrollClassInfo *>(classinfo); + if (sci->scroll_type == XIScrollTypeVertical) { + scrollingDevice.orientations |= Qt::Vertical; + scrollingDevice.verticalIndex = sci->number; + scrollingDevice.verticalIncrement = sci->increment; } - case XIButtonClass: { - XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(devices[i].classes[c]); - if (bci->num_buttons >= 5) { - Atom label4 = bci->labels[3]; - Atom label5 = bci->labels[4]; - // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on - // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons. - if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) && - (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown)) - scrollingDevice.legacyOrientations |= Qt::Vertical; - } - if (bci->num_buttons >= 7) { - Atom label6 = bci->labels[5]; - Atom label7 = bci->labels[6]; - if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) - scrollingDevice.legacyOrientations |= Qt::Horizontal; - } - qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons); - break; + else if (sci->scroll_type == XIScrollTypeHorizontal) { + scrollingDevice.orientations |= Qt::Horizontal; + scrollingDevice.horizontalIndex = sci->number; + scrollingDevice.horizontalIncrement = sci->increment; + } + break; + } + case XIButtonClass: { + XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(classinfo); + if (bci->num_buttons >= 5) { + Atom label4 = bci->labels[3]; + Atom label5 = bci->labels[4]; + // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on + // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons. + if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) && + (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown)) + scrollingDevice.legacyOrientations |= Qt::Vertical; } + if (bci->num_buttons >= 7) { + Atom label6 = bci->labels[5]; + Atom label7 = bci->labels[6]; + if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) + scrollingDevice.legacyOrientations |= Qt::Horizontal; + } + qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons); + break; + } #endif - case XIKeyClass: - qCDebug(lcQpaXInputDevices) << " it's a keyboard"; - break; + case XIKeyClass: + qCDebug(lcQpaXInputDevices) << " it's a keyboard"; + break; #ifdef XCB_USE_XINPUT22 - case XITouchClass: - // will be handled in deviceForId() - break; + case XITouchClass: + // will be handled in populateTouchDevices() + break; #endif - default: - qCDebug(lcQpaXInputDevices) << " has class" << devices[i].classes[c]->type; - break; - } + default: + qCDebug(lcQpaXInputDevices) << " has class" << classinfo->type; + break; } - bool isTablet = false; + } + bool isTablet = false; #if QT_CONFIG(tabletevent) - // If we have found the valuators which we expect a tablet to have, it might be a tablet. - if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) && - tabletData.valuatorInfo.contains(QXcbAtom::AbsY) && - tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure)) - isTablet = true; - - // But we need to be careful not to take the touch and tablet-button devices as tablets. - QByteArray name = QByteArray(devices[i].name).toLower(); - QString dbgType = QLatin1String("UNKNOWN"); - if (name.contains("eraser")) { - isTablet = true; - tabletData.pointerType = QTabletEvent::Eraser; - dbgType = QLatin1String("eraser"); - } else if (name.contains("cursor") && !(name.contains("cursor controls") && name.contains("trackball"))) { - isTablet = true; - tabletData.pointerType = QTabletEvent::Cursor; - dbgType = QLatin1String("cursor"); - } else if (name.contains("wacom") && name.contains("finger touch")) { - isTablet = false; - } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { - // combined device (evdev) rather than separate pen/eraser (wacom driver) - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { - // some "Genius" tablets - isTablet = true; - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("waltop") && name.contains("tablet")) { - // other "Genius" tablets - // WALTOP International Corp. Slim Tablet - isTablet = true; - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("uc-logic") && isTablet) { - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else { - isTablet = false; - } + // If we have found the valuators which we expect a tablet to have, it might be a tablet. + if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) && + tabletData.valuatorInfo.contains(QXcbAtom::AbsY) && + tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure)) + isTablet = true; + + // But we need to be careful not to take the touch and tablet-button devices as tablets. + QByteArray name = QByteArray(deviceInfo->name).toLower(); + QString dbgType = QLatin1String("UNKNOWN"); + if (name.contains("eraser")) { + isTablet = true; + tabletData.pointerType = QTabletEvent::Eraser; + dbgType = QLatin1String("eraser"); + } else if (name.contains("cursor") && !(name.contains("cursor controls") && name.contains("trackball"))) { + isTablet = true; + tabletData.pointerType = QTabletEvent::Cursor; + dbgType = QLatin1String("cursor"); + } else if (name.contains("wacom") && name.contains("finger touch")) { + isTablet = false; + } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { + // combined device (evdev) rather than separate pen/eraser (wacom driver) + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { + // some "Genius" tablets + isTablet = true; + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("waltop") && name.contains("tablet")) { + // other "Genius" tablets + // WALTOP International Corp. Slim Tablet + isTablet = true; + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("uc-logic") && isTablet) { + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else { + isTablet = false; + } - if (isTablet) { - tabletData.deviceId = devices[i].deviceid; - m_tabletData.append(tabletData); - qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; - } + if (isTablet) { + tabletData.deviceId = deviceInfo->deviceid; + m_tabletData.append(tabletData); + qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; + } #endif // QT_CONFIG(tabletevent) #ifdef XCB_USE_XINPUT21 - if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) { - scrollingDevice.deviceId = devices[i].deviceid; - // Only use legacy wheel button events when we don't have real scroll valuators. - scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations; - m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice); - qCDebug(lcQpaXInputDevices) << " it's a scrolling device"; - } + if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) { + scrollingDevice.deviceId = deviceInfo->deviceid; + // Only use legacy wheel button events when we don't have real scroll valuators. + scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations; + m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice); + qCDebug(lcQpaXInputDevices) << " it's a scrolling device"; + } #endif - if (!isTablet) { - // touchDeviceForId populates XInput2DeviceData the first time it is called - // with a new deviceId. On subsequent calls it will return the cached object. - XInput2TouchDeviceData *dev = touchDeviceForId(devices[i].deviceid); - if (dev && lcQpaXInputDevices().isDebugEnabled()) { - if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen) - qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", - dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), - dev->qtTouchDevice->maximumTouchPoints()); - else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad) - qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f", - dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), - dev->qtTouchDevice->maximumTouchPoints(), - dev->size.width(), dev->size.height()); - } + if (!isTablet) { + TouchDeviceData *dev = populateTouchDevices(deviceInfo); + if (dev && lcQpaXInputDevices().isDebugEnabled()) { + if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen) + qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", + dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), + dev->qtTouchDevice->maximumTouchPoints()); + else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad) + qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f", + dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), + dev->qtTouchDevice->maximumTouchPoints(), + dev->size.width(), dev->size.height()); } } - XIFreeDeviceInfo(devices); + } -void QXcbConnection::finalizeXInput2() +void QXcbConnection::xi2SetupDevices() { - for (XInput2TouchDeviceData *dev : qAsConst(m_touchDevices)) { - if (dev->xiDeviceInfo) - XIFreeDeviceInfo(dev->xiDeviceInfo); - delete dev; +#if QT_CONFIG(tabletevent) + m_tabletData.clear(); +#endif + m_scrollingDevices.clear(); + m_touchDevices.clear(); + + Display *xDisplay = static_cast<Display *>(m_xlib_display); + int deviceCount = 0; + XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount); + for (int i = 0; i < deviceCount; ++i) { + // Only non-master pointing devices are relevant here. + if (devices[i].use != XISlavePointer) + continue; + xi2SetupDevice(&devices[i], false); } + XIFreeDeviceInfo(devices); } -void QXcbConnection::xi2Select(xcb_window_t window) +/*! \internal + + Notes on QT_XCB_NO_XI2_MOUSE Handling: + + Here we don't select pointer button press/release and motion events on master devices, instead + we select these events directly on slave devices. This means that a master device will fallback + to sending core events for every XI_* event that is sent directly by a slave device. For more + details see "Event processing for attached slave devices" in XInput2 specification. To prevent + handling of the same event twice, we have checks for xi2MouseEventsDisabled() in XI2 event + handlers (but this is somewhat inconsistent in some situations). If the purpose for + QT_XCB_NO_XI2_MOUSE was so that an application using QAbstractNativeEventFilter would see core + mouse events before they are handled by Qt then QT_XCB_NO_XI2_MOUSE won't always work as + expected (e.g. we handle scroll event directly from a slave device event, before an application + has seen the fallback core event from a master device). + + The commit introducing QT_XCB_NO_XI2_MOUSE also states that setting this envvar "restores the + old behavior with broken grabbing". It did not elaborate why grabbing was not fixed for this + code path. The issue that this envvar tries to solve seem to be less important than broken + grabbing (broken apparently only for touch events). Thus, if you really want core mouse events + in your application and do not care about broken touch, then use QT_XCB_NO_XI2 (more on this + below) to disable the extension all together. The reason why grabbing might have not been fixed + is that calling XIGrabDevice with this code path for some reason always returns AlreadyGrabbed + (by debugging X server's code it appears that when we call XIGrabDevice, an X server first grabs + pointer via core pointer and then fails to do XI2 grab with AlreadyGrabbed; disclaimer - I did + not debug this in great detail). When we try supporting odd setups like QT_XCB_NO_XI2_MOUSE, we + are asking for trouble anyways. + + In conclusion, introduction of QT_XCB_NO_XI2_MOUSE causes more issues than solves - the above + mentioned inconsistencies, maintenance of this code path and that QT_XCB_NO_XI2_MOUSE replaces + less important issue with somewhat more important issue. It also makes us to use less optimal + code paths in certain situations (see xi2HandleHierarchyEvent). Using of QT_XCB_NO_XI2 has its + drawbacks too - no tablet and touch events. So the only real fix in this case is at an + application side (teach the application about xcb_ge_event_t events). Based on this, + QT_XCB_NO_XI2_MOUSE will be removed in ### Qt 6. It should not have existed in the first place, + native events seen by QAbstractNativeEventFilter is not really a public API, applications should + expect changes at this level and do ifdefs if something changes between Qt version. +*/ +void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window) { - if (!m_xi2Enabled || window == rootWindow()) + if (window == rootWindow()) return; - Display *xDisplay = static_cast<Display *>(m_xlib_display); - unsigned int bitMask = 0; - unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask); + unsigned int mask = 0; + unsigned char *bitMask = reinterpret_cast<unsigned char *>(&mask); + Display *dpy = static_cast<Display *>(m_xlib_display); #ifdef XCB_USE_XINPUT22 if (isAtLeastXI22()) { - bitMask |= XI_TouchBeginMask; - bitMask |= XI_TouchUpdateMask; - bitMask |= XI_TouchEndMask; - bitMask |= XI_PropertyEventMask; // for tablets - if (xi2MouseEvents()) { - // We want both mouse and touch through XI2 if touch is supported (>= 2.2). - // The plain xcb press and motion events will not be delivered after this. - bitMask |= XI_ButtonPressMask; - bitMask |= XI_ButtonReleaseMask; - bitMask |= XI_MotionMask; - - // There is a check for enter/leave events in plain xcb enter/leave event handler - bitMask |= XI_EnterMask; - bitMask |= XI_LeaveMask; - - qCDebug(lcQpaXInput, "XInput 2.2: Selecting press/release/motion events in addition to touch"); - } - XIEventMask mask; - mask.mask_len = sizeof(bitMask); - mask.mask = xiBitMask; - // When xi2MouseEvents() is true (the default), pointer emulation for touch and tablet - // events will get disabled. This is preferable, as Qt Quick handles touch events - // directly, while for other applications QtGui synthesizes mouse events. - mask.deviceid = XIAllMasterDevices; - Status result = XISelectEvents(xDisplay, window, &mask, 1); + mask |= XI_TouchBeginMask; + mask |= XI_TouchUpdateMask; + mask |= XI_TouchEndMask; + + XIEventMask xiMask; + xiMask.mask_len = sizeof(mask); + xiMask.mask = bitMask; + xiMask.deviceid = XIAllMasterDevices; + Status result = XISelectEvents(dpy, window, &xiMask, 1); if (result == Success) QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); else - qCDebug(lcQpaXInput, "XInput 2.2: failed to select pointer/touch events, window %x, result %d", window, result); + qCDebug(lcQpaXInput, "failed to select events, window %x, result %d", window, result); } +#endif - const bool pointerSelected = isAtLeastXI22() && xi2MouseEvents(); -#else - const bool pointerSelected = false; -#endif // XCB_USE_XINPUT22 + mask = XI_ButtonPressMask; + mask |= XI_ButtonReleaseMask; + mask |= XI_MotionMask; - QSet<int> tabletDevices; #if QT_CONFIG(tabletevent) + QSet<int> tabletDevices; if (!m_tabletData.isEmpty()) { - unsigned int tabletBitMask; - unsigned char *xiTabletBitMask = reinterpret_cast<unsigned char *>(&tabletBitMask); - QVector<XIEventMask> xiEventMask(m_tabletData.count()); - tabletBitMask = XI_PropertyEventMask; - if (!pointerSelected) - tabletBitMask |= XI_ButtonPressMask | XI_ButtonReleaseMask | XI_MotionMask; - for (int i = 0; i < m_tabletData.count(); ++i) { + const int nrTablets = m_tabletData.count(); + QVector<XIEventMask> xiEventMask(nrTablets); + for (int i = 0; i < nrTablets; ++i) { int deviceId = m_tabletData.at(i).deviceId; tabletDevices.insert(deviceId); xiEventMask[i].deviceid = deviceId; - xiEventMask[i].mask_len = sizeof(tabletBitMask); - xiEventMask[i].mask = xiTabletBitMask; + xiEventMask[i].mask_len = sizeof(mask); + xiEventMask[i].mask = bitMask; } - XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count()); + XISelectEvents(dpy, window, xiEventMask.data(), nrTablets); } -#endif // QT_CONFIG(tabletevent) +#endif #ifdef XCB_USE_XINPUT21 - // Enable each scroll device - if (!m_scrollingDevices.isEmpty() && !pointerSelected) { - // Only when XI2 mouse events are not enabled, otherwise motion and release are selected already. + if (!m_scrollingDevices.isEmpty()) { QVector<XIEventMask> xiEventMask(m_scrollingDevices.size()); - unsigned int scrollBitMask; - unsigned char *xiScrollBitMask = reinterpret_cast<unsigned char *>(&scrollBitMask); - - scrollBitMask = XI_MotionMask; - scrollBitMask |= XI_ButtonReleaseMask; - int i=0; + int i = 0; for (const ScrollingDevice& scrollingDevice : qAsConst(m_scrollingDevices)) { +#if QT_CONFIG(tabletevent) if (tabletDevices.contains(scrollingDevice.deviceId)) continue; // All necessary events are already captured. +#endif xiEventMask[i].deviceid = scrollingDevice.deviceId; - xiEventMask[i].mask_len = sizeof(scrollBitMask); - xiEventMask[i].mask = xiScrollBitMask; + xiEventMask[i].mask_len = sizeof(mask); + xiEventMask[i].mask = bitMask; i++; } - XISelectEvents(xDisplay, window, xiEventMask.data(), i); + XISelectEvents(dpy, window, xiEventMask.data(), i); } -#else - Q_UNUSED(xiBitMask); +#endif + +#if !QT_CONFIG(tabletevent) && !defined(XCB_USE_XINPUT21) + Q_UNUSED(bitMask); + Q_UNUSED(dpy); #endif } -XInput2TouchDeviceData *QXcbConnection::touchDeviceForId(int id) +QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id) { - XInput2TouchDeviceData *dev = Q_NULLPTR; - QHash<int, XInput2TouchDeviceData*>::const_iterator devIt = m_touchDevices.constFind(id); - if (devIt != m_touchDevices.cend()) { - dev = devIt.value(); - } else { - int nrDevices = 0; - QTouchDevice::Capabilities caps = 0; - dev = new XInput2TouchDeviceData; - dev->xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), id, &nrDevices); - if (nrDevices <= 0) { - delete dev; - return 0; - } - int type = -1; - int maxTouchPoints = 1; - bool hasRelativeCoords = false; - for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { - XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; - switch (classinfo->type) { + TouchDeviceData *dev = nullptr; + if (m_touchDevices.contains(id)) + dev = &m_touchDevices[id]; + return dev; +} + +QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info) +{ + XIDeviceInfo *deviceinfo = reinterpret_cast<XIDeviceInfo *>(info); + QTouchDevice::Capabilities caps = 0; + int type = -1; + int maxTouchPoints = 1; + bool isTouchDevice = false; + bool hasRelativeCoords = false; + TouchDeviceData dev; + for (int i = 0; i < deviceinfo->num_classes; ++i) { + XIAnyClassInfo *classinfo = deviceinfo->classes[i]; + switch (classinfo->type) { #ifdef XCB_USE_XINPUT22 - case XITouchClass: { - XITouchClassInfo *tci = reinterpret_cast<XITouchClassInfo *>(classinfo); - maxTouchPoints = tci->num_touches; - qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode); - switch (tci->mode) { - case XIDependentTouch: - type = QTouchDevice::TouchPad; - break; - case XIDirectTouch: - type = QTouchDevice::TouchScreen; - break; - } + case XITouchClass: { + XITouchClassInfo *tci = reinterpret_cast<XITouchClassInfo *>(classinfo); + maxTouchPoints = tci->num_touches; + qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode); + switch (tci->mode) { + case XIDependentTouch: + type = QTouchDevice::TouchPad; + break; + case XIDirectTouch: + type = QTouchDevice::TouchScreen; break; } + break; + } #endif // XCB_USE_XINPUT22 - case XIValuatorClass: { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); - // Some devices (mice) report a resolution of 0; they will be excluded later, - // for now just prevent a division by zero - const int vciResolution = vci->resolution ? vci->resolution : 1; - if (vci->label == atom(QXcbAtom::AbsMTPositionX)) - caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition; - else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) - caps |= QTouchDevice::Area; - else if (vci->label == atom(QXcbAtom::AbsMTOrientation)) - dev->providesTouchOrientation = true; - else if (vci->label == atom(QXcbAtom::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure)) - caps |= QTouchDevice::Pressure; - else if (vci->label == atom(QXcbAtom::RelX)) { - hasRelativeCoords = true; - dev->size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); - } else if (vci->label == atom(QXcbAtom::RelY)) { - hasRelativeCoords = true; - dev->size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); - } else if (vci->label == atom(QXcbAtom::AbsX)) { - caps |= QTouchDevice::Position; - dev->size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); - } else if (vci->label == atom(QXcbAtom::AbsY)) { - caps |= QTouchDevice::Position; - dev->size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); - } - break; + case XIValuatorClass: { + XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); + const QXcbAtom::Atom valuatorAtom = qatom(vci->label); + if (valuatorAtom < QXcbAtom::NAtoms) { + TouchDeviceData::ValuatorClassInfo info; + info.min = vci->min; + info.max = vci->max; + info.number = vci->number; + info.label = valuatorAtom; + dev.valuatorInfo.append(info); } - default: - break; + // Some devices (mice) report a resolution of 0; they will be excluded later, + // for now just prevent a division by zero + const int vciResolution = vci->resolution ? vci->resolution : 1; + if (valuatorAtom == QXcbAtom::AbsMTPositionX) + caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition; + else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor) + caps |= QTouchDevice::Area; + else if (valuatorAtom == QXcbAtom::AbsMTOrientation) + dev.providesTouchOrientation = true; + else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure) + caps |= QTouchDevice::Pressure; + else if (valuatorAtom == QXcbAtom::RelX) { + hasRelativeCoords = true; + dev.size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); + } else if (valuatorAtom == QXcbAtom::RelY) { + hasRelativeCoords = true; + dev.size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); + } else if (valuatorAtom == QXcbAtom::AbsX) { + caps |= QTouchDevice::Position; + dev.size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); + } else if (valuatorAtom == QXcbAtom::AbsY) { + caps |= QTouchDevice::Position; + dev.size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); } + break; } - if (type < 0 && caps && hasRelativeCoords) { - type = QTouchDevice::TouchPad; - if (dev->size.width() < 10 || dev->size.height() < 10 || - dev->size.width() > 10000 || dev->size.height() > 10000) - dev->size = QSizeF(130, 110); - } - if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) - caps |= QTouchDevice::MouseEmulation; - - if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) { - dev->qtTouchDevice = new QTouchDevice; - dev->qtTouchDevice->setName(QString::fromUtf8(dev->xiDeviceInfo->name)); - dev->qtTouchDevice->setType((QTouchDevice::DeviceType)type); - dev->qtTouchDevice->setCapabilities(caps); - dev->qtTouchDevice->setMaximumTouchPoints(maxTouchPoints); - if (caps != 0) - QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice); - m_touchDevices[id] = dev; - } else { - XIFreeDeviceInfo(dev->xiDeviceInfo); - delete dev; - dev = 0; + default: + break; } } - return dev; + if (type < 0 && caps && hasRelativeCoords) { + type = QTouchDevice::TouchPad; + if (dev.size.width() < 10 || dev.size.height() < 10 || + dev.size.width() > 10000 || dev.size.height() > 10000) + dev.size = QSizeF(130, 110); + } + if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) + caps |= QTouchDevice::MouseEmulation; + + if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) { + dev.qtTouchDevice = new QTouchDevice; + dev.qtTouchDevice->setName(QString::fromUtf8(deviceinfo->name)); + dev.qtTouchDevice->setType((QTouchDevice::DeviceType)type); + dev.qtTouchDevice->setCapabilities(caps); + dev.qtTouchDevice->setMaximumTouchPoints(maxTouchPoints); + if (caps != 0) + QWindowSystemInterface::registerTouchDevice(dev.qtTouchDevice); + m_touchDevices[deviceinfo->deviceid] = dev; + isTouchDevice = true; + } + + return isTouchDevice ? &m_touchDevices[deviceinfo->deviceid] : nullptr; } #if defined(XCB_USE_XINPUT21) || QT_CONFIG(tabletevent) @@ -525,7 +582,7 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) break; } case XI_HierarchyChanged: - xi2HandleHierachyEvent(xiEvent); + xi2HandleHierarchyEvent(xiEvent); return; case XI_DeviceChanged: xi2HandleDeviceChangedEvent(xiEvent); @@ -549,9 +606,8 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) #endif // QT_CONFIG(tabletevent) #ifdef XCB_USE_XINPUT21 - QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(sourceDeviceId); - if (device != m_scrollingDevices.end()) - xi2HandleScrollEvent(xiEvent, device.value()); + if (ScrollingDevice *device = scrollingDeviceForId(sourceDeviceId)) + xi2HandleScrollEvent(xiEvent, *device); #endif // XCB_USE_XINPUT21 #ifdef XCB_USE_XINPUT22 @@ -560,7 +616,7 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) case XI_ButtonPress: case XI_ButtonRelease: case XI_Motion: - if (xi2MouseEvents() && eventListener && !(xiDeviceEvent->flags & XIPointerEmulated)) + if (!xi2MouseEventsDisabled() && eventListener && !(xiDeviceEvent->flags & XIPointerEmulated)) eventListener->handleXIMouseEvent(event); break; @@ -576,7 +632,7 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) xi2ProcessTouch(xiDeviceEvent, platformWindow); break; } - } else if (xiEnterEvent && xi2MouseEvents() && eventListener) { + } else if (xiEnterEvent && !xi2MouseEventsDisabled() && eventListener) { switch (xiEnterEvent->evtype) { case XI_Enter: case XI_Leave: @@ -587,20 +643,25 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) #endif // XCB_USE_XINPUT22 } +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; +} + #ifdef XCB_USE_XINPUT22 -static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci) +bool QXcbConnection::isTouchScreen(int id) { - if (value > vci->max) - value = vci->max; - if (value < vci->min) - value = vci->min; - return (value - vci->min) / (vci->max - vci->min); + auto device = touchDeviceForId(id); + return device && device->qtTouchDevice->type() == QTouchDevice::TouchScreen; } void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow) { xXIDeviceEvent *xiDeviceEvent = static_cast<xXIDeviceEvent *>(xiDevEvent); - XInput2TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid); + TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid); Q_ASSERT(dev); const bool firstTouch = dev->touchPoints.isEmpty(); if (xiDeviceEvent->evtype == XI_TouchBegin) { @@ -617,53 +678,53 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo qreal nx = -1.0, ny = -1.0; qreal w = 0.0, h = 0.0; bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width(); - for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { - XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; - if (classinfo->type == XIValuatorClass) { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); - int n = vci->number; - double value; - if (!xi2GetValuatorValueIfSet(xiDeviceEvent, n, &value)) - continue; - if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) - qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf", - atomName(vci->label).constData(), value, vci->min, vci->max ); - if (vci->label == atom(QXcbAtom::RelX)) { - nx = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::RelY)) { - ny = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsX)) { - nx = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsY)) { - ny = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsMTPositionX)) { - nx = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsMTPositionY)) { - ny = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) { - const qreal sw = screen->geometry().width(); - const qreal sh = screen->geometry().height(); - w = valuatorNormalized(value, vci) * std::sqrt(sw * sw + sh * sh); - } else if (vci->label == atom(QXcbAtom::AbsMTTouchMinor)) { - const qreal sw = screen->geometry().width(); - const qreal sh = screen->geometry().height(); - h = valuatorNormalized(value, vci) * std::sqrt(sw * sw + sh * sh); - } else if (vci->label == atom(QXcbAtom::AbsMTOrientation)) { - // Find the closest axis. - // 0 corresponds to the Y axis, vci->max to the X axis. - // Flipping over the Y axis and rotating by 180 degrees - // don't change the result, so normalize value to range - // [0, vci->max] first. - value = qAbs(value); - while (value > vci->max) - value -= 2 * vci->max; - value = qAbs(value); - majorAxisIsY = value < vci->max - value; - } else if (vci->label == atom(QXcbAtom::AbsMTPressure) || - vci->label == atom(QXcbAtom::AbsPressure)) { - touchPoint.pressure = valuatorNormalized(value, vci); - } + for (const TouchDeviceData::ValuatorClassInfo vci : dev->valuatorInfo) { + double value; + if (!xi2GetValuatorValueIfSet(xiDeviceEvent, vci.number, &value)) + continue; + if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) + qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf", + atomName(vci.label).constData(), value, vci.min, vci.max); + if (value > vci.max) + value = vci.max; + if (value < vci.min) + value = vci.min; + qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min); + if (vci.label == QXcbAtom::RelX) { + nx = valuatorNormalized; + } else if (vci.label == QXcbAtom::RelY) { + ny = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsX) { + nx = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsY) { + ny = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsMTPositionX) { + nx = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsMTPositionY) { + ny = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsMTTouchMajor) { + const qreal sw = screen->geometry().width(); + const qreal sh = screen->geometry().height(); + w = valuatorNormalized * std::sqrt(sw * sw + sh * sh); + } else if (vci.label == QXcbAtom::AbsMTTouchMinor) { + const qreal sw = screen->geometry().width(); + const qreal sh = screen->geometry().height(); + h = valuatorNormalized * std::sqrt(sw * sw + sh * sh); + } else if (vci.label == QXcbAtom::AbsMTOrientation) { + // Find the closest axis. + // 0 corresponds to the Y axis, vci.max to the X axis. + // Flipping over the Y axis and rotating by 180 degrees + // don't change the result, so normalize value to range + // [0, vci.max] first. + value = qAbs(value); + while (value > vci.max) + value -= 2 * vci.max; + value = qAbs(value); + majorAxisIsY = value < vci.max - value; + } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) { + touchPoint.pressure = valuatorNormalized; } + } // If any value was not updated, use the last-known value. if (nx == -1.0) { @@ -766,12 +827,12 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo bool QXcbConnection::startSystemResizeForTouchBegin(xcb_window_t window, const QPoint &point, Qt::Corner corner) { - QHash<int, XInput2TouchDeviceData*>::const_iterator devIt = m_touchDevices.constBegin(); + QHash<int, TouchDeviceData>::const_iterator devIt = m_touchDevices.constBegin(); for (; devIt != m_touchDevices.constEnd(); ++devIt) { - XInput2TouchDeviceData *deviceData = devIt.value(); - if (deviceData->qtTouchDevice->type() == QTouchDevice::TouchScreen) { - QHash<int, QPointF>::const_iterator pointIt = deviceData->pointPressedPosition.constBegin(); - for (; pointIt != deviceData->pointPressedPosition.constEnd(); ++pointIt) { + TouchDeviceData deviceData = devIt.value(); + if (deviceData.qtTouchDevice->type() == QTouchDevice::TouchScreen) { + QHash<int, QPointF>::const_iterator pointIt = deviceData.pointPressedPosition.constBegin(); + for (; pointIt != deviceData.pointPressedPosition.constEnd(); ++pointIt) { if (pointIt.value().toPoint() == point) { m_startSystemResizeInfo.window = window; m_startSystemResizeInfo.deviceid = devIt.key(); @@ -784,6 +845,7 @@ bool QXcbConnection::startSystemResizeForTouchBegin(xcb_window_t window, const Q } return false; } +#endif // XCB_USE_XINPUT22 bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) { @@ -844,59 +906,70 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) return grabbed; } -#endif // XCB_USE_XINPUT22 -void QXcbConnection::xi2HandleHierachyEvent(void *event) +void QXcbConnection::xi2HandleHierarchyEvent(void *event) { xXIHierarchyEvent *xiEvent = reinterpret_cast<xXIHierarchyEvent *>(event); // We only care about hotplugged devices if (!(xiEvent->flags & (XISlaveRemoved | XISlaveAdded))) return; + xi2SetupDevices(); - // Reselect events for all event-listening windows. - for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it) - xi2Select(it.key()); + + if (xi2MouseEventsDisabled()) { + // In compatibility mode (a.k.a xi2MouseEventsDisabled() mode) we select events for + // each device separately. When a new device appears, we have to select events from + // this device on all event-listening windows. This is not needed when events are + // selected via XIAllDevices/XIAllMasterDevices (as in xi2SelectDeviceEvents()). + for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it) + xi2SelectDeviceEventsCompatibility(it.key()); + } } void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) { xXIDeviceChangedEvent *xiEvent = reinterpret_cast<xXIDeviceChangedEvent *>(event); - - // ### If a slave device changes (XIDeviceChange), we should probably run setup on it again. - if (xiEvent->reason != XISlaveSwitch) - return; - + switch (xiEvent->reason) { + case XIDeviceChange: { + int nrDevices = 0; + Display *dpy = static_cast<Display *>(m_xlib_display); + XIDeviceInfo* deviceInfo = XIQueryDevice(dpy, xiEvent->sourceid, &nrDevices); + if (nrDevices <= 0) + return; + xi2SetupDevice(deviceInfo); + XIFreeDeviceInfo(deviceInfo); + break; + } + case XISlaveSwitch: { #ifdef XCB_USE_XINPUT21 - // This code handles broken scrolling device drivers that reset absolute positions - // when they are made active. Whenever a new slave device is made active the - // primary pointer sends a DeviceChanged event with XISlaveSwitch, and the new - // active slave in sourceid. - - QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(xiEvent->sourceid); - if (device == m_scrollingDevices.end()) - return; + if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid)) + xi2UpdateScrollingDevice(*scrollingDevice); +#endif + break; + } + default: + qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid); + break; + } +} +#ifdef XCB_USE_XINPUT21 +void QXcbConnection::xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice) +{ int nrDevices = 0; - XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), xiEvent->sourceid, &nrDevices); + Display *dpy = static_cast<Display *>(m_xlib_display); + XIDeviceInfo* deviceInfo = XIQueryDevice(dpy, scrollingDevice.deviceId, &nrDevices); if (nrDevices <= 0) { - qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", xiEvent->sourceid); + qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId); return; } - updateScrollingDevice(*device, xiDeviceInfo->num_classes, xiDeviceInfo->classes); - XIFreeDeviceInfo(xiDeviceInfo); -#endif -} - -void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int num_classes, void *classInfo) -{ -#ifdef XCB_USE_XINPUT21 - XIAnyClassInfo **classes = reinterpret_cast<XIAnyClassInfo**>(classInfo); QPointF lastScrollPosition; if (lcQpaXInput().isDebugEnabled()) lastScrollPosition = scrollingDevice.lastScrollPosition; - for (int c = 0; c < num_classes; ++c) { - if (classes[c]->type == XIValuatorClass) { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classes[c]); + for (int c = 0; c < deviceInfo->num_classes; ++c) { + XIAnyClassInfo *classInfo = deviceInfo->classes[c]; + if (classInfo->type == XIValuatorClass) { + XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classInfo); const int valuatorAtom = qatom(vci->label); if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) scrollingDevice.lastScrollPosition.setX(vci->value); @@ -909,37 +982,30 @@ void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int lastScrollPosition.x(), lastScrollPosition.y(), scrollingDevice.lastScrollPosition.x(), scrollingDevice.lastScrollPosition.y()); -#else - Q_UNUSED(scrollingDevice); - Q_UNUSED(num_classes); - Q_UNUSED(classInfo); -#endif + + XIFreeDeviceInfo(deviceInfo); } -#ifdef XCB_USE_XINPUT21 -void QXcbConnection::handleEnterEvent() +void QXcbConnection::xi2UpdateScrollingDevices() { QHash<int, ScrollingDevice>::iterator it = m_scrollingDevices.begin(); const QHash<int, ScrollingDevice>::iterator end = m_scrollingDevices.end(); while (it != end) { - ScrollingDevice& scrollingDevice = it.value(); - int nrDevices = 0; - XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), scrollingDevice.deviceId, &nrDevices); - if (nrDevices <= 0) { - qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId); - it = m_scrollingDevices.erase(it); - continue; - } - updateScrollingDevice(scrollingDevice, xiDeviceInfo->num_classes, xiDeviceInfo->classes); - XIFreeDeviceInfo(xiDeviceInfo); + xi2UpdateScrollingDevice(it.value()); ++it; } } -#endif + +QXcbConnection::ScrollingDevice *QXcbConnection::scrollingDeviceForId(int id) +{ + ScrollingDevice *dev = nullptr; + if (m_scrollingDevices.contains(id)) + dev = &m_scrollingDevices[id]; + return dev; +} void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice) { -#ifdef XCB_USE_XINPUT21 xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event); if (xiEvent->evtype == XI_Motion && scrollingDevice.orientations) { @@ -1009,10 +1075,51 @@ void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollin } } } -#else - Q_UNUSED(event); - Q_UNUSED(scrollingDevice); +} #endif // XCB_USE_XINPUT21 + +static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number) +{ + int offset = 0; + for (int i = 0; i < maskLen; i++) { + if (number < 8) { + if ((maskPtr[i] & (1 << number)) == 0) + return -1; + } + for (int j = 0; j < 8; j++) { + if (j == number) + return offset; + if (maskPtr[i] & (1 << j)) + offset++; + } + number -= 8; + } + return -1; +} + +bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value) +{ + const xXIDeviceEvent *xideviceevent = static_cast<const xXIDeviceEvent *>(event); + const unsigned char *buttonsMaskAddr = (const unsigned char*)&xideviceevent[1]; + const unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4; + FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4); + + int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum); + if (valuatorOffset < 0) + return false; + + *value = valuatorsValuesAddr[valuatorOffset].integral; + *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16)); + return true; +} + +void QXcbConnection::xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event) +{ + // xcb event structs contain stuff that wasn't on the wire, the full_sequence field + // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. + // Move this data back to have the same layout in memory as it was on the wire + // and allow casting, overwriting the full_sequence field. + memmove((char*) event + 32, (char*) event + 36, event->length * 4); } Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b) @@ -1029,15 +1136,6 @@ Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b) return Qt::NoButton; } -#ifdef XCB_USE_XINPUT22 -bool QXcbConnection::isTouchScreen(int id) const -{ - auto device = m_touchDevices.value(id); - return device && device->qtTouchDevice - && device->qtTouchDevice->type() == QTouchDevice::TouchScreen; -} -#endif - #if QT_CONFIG(tabletevent) static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) { // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp index 7c62c2e2b3..da63360333 100644 --- a/src/plugins/platforms/xcb/qxcbcursor.cpp +++ b/src/plugins/platforms/xcb/qxcbcursor.cpp @@ -579,7 +579,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) if (cursor) return cursor; if (!cursor && cursorId) { - cursor = XCreateFontCursor(DISPLAY_FROM_XCB(this), cursorId); + cursor = XCreateFontCursor(static_cast<Display *>(connection()->xlib_display()), cursorId); if (cursor) return cursor; } @@ -631,10 +631,9 @@ void QXcbCursor::queryPointer(QXcbConnection *c, QXcbVirtualDesktop **virtualDes *pos = QPoint(); xcb_window_t root = c->primaryVirtualDesktop()->root(); - xcb_query_pointer_cookie_t cookie = xcb_query_pointer(c->xcb_connection(), root); - xcb_generic_error_t *err = 0; - xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c->xcb_connection(), cookie, &err); - if (!err && reply) { + + auto reply = Q_XCB_REPLY(xcb_query_pointer, c->xcb_connection(), root); + if (reply) { if (virtualDesktop) { const auto virtualDesktops = c->virtualDesktops(); for (QXcbVirtualDesktop *vd : virtualDesktops) { @@ -648,11 +647,8 @@ void QXcbCursor::queryPointer(QXcbConnection *c, QXcbVirtualDesktop **virtualDes *pos = QPoint(reply->root_x, reply->root_y); if (keybMask) *keybMask = reply->mask; - free(reply); return; } - free(err); - free(reply); } QPoint QXcbCursor::pos() const diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index 51d09d8927..1e963268ef 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -94,32 +94,27 @@ static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w) { xcb_window_t proxy = XCB_NONE; - xcb_get_property_cookie_t cookie = Q_XCB_CALL2(xcb_get_property(c->xcb_connection(), false, w, c->atom(QXcbAtom::XdndProxy), - XCB_ATOM_WINDOW, 0, 1), c); - xcb_get_property_reply_t *reply = xcb_get_property_reply(c->xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), + false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1); if (reply && reply->type == XCB_ATOM_WINDOW) - proxy = *((xcb_window_t *)xcb_get_property_value(reply)); - free(reply); + proxy = *((xcb_window_t *)xcb_get_property_value(reply.get())); if (proxy == XCB_NONE) return proxy; // exists and is real? - cookie = Q_XCB_CALL2(xcb_get_property(c->xcb_connection(), false, proxy, c->atom(QXcbAtom::XdndProxy), - XCB_ATOM_WINDOW, 0, 1), c); - reply = xcb_get_property_reply(c->xcb_connection(), cookie, 0); + reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), + false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1); if (reply && reply->type == XCB_ATOM_WINDOW) { - xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply)); + xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply.get())); if (proxy != p) proxy = 0; } else { proxy = 0; } - free(reply); - return proxy; } @@ -142,7 +137,7 @@ protected: QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c) { - dropData = new QXcbDropData(this); + m_dropData = new QXcbDropData(this); init(); cleanup_timer = -1; @@ -150,7 +145,7 @@ QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c) QXcbDrag::~QXcbDrag() { - delete dropData; + delete m_dropData; } void QXcbDrag::init() @@ -175,11 +170,6 @@ void QXcbDrag::init() canceled = false; } -QMimeData *QXcbDrag::platformDropData() -{ - return dropData; -} - bool QXcbDrag::eventFilter(QObject *o, QEvent *e) { /* We are setting a mouse grab on the QShapedPixmapWindow in order not to @@ -235,28 +225,19 @@ void QXcbDrag::endDrag() initiatorWindow.clear(); } -static xcb_translate_coordinates_reply_t * -translateCoordinates(QXcbConnection *c, xcb_window_t from, xcb_window_t to, int x, int y) -{ - xcb_translate_coordinates_cookie_t cookie = - xcb_translate_coordinates(c->xcb_connection(), from, to, x, y); - return xcb_translate_coordinates_reply(c->xcb_connection(), cookie, 0); -} - static bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType) { bool interacts = false; - xcb_shape_get_rectangles_reply_t *reply = xcb_shape_get_rectangles_reply(connection, xcb_shape_get_rectangles(connection, w, shapeType), NULL); + auto reply = Q_XCB_REPLY(xcb_shape_get_rectangles, connection, w, shapeType); if (reply) { - xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply); + xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply.get()); if (rectangles) { - const int nRectangles = xcb_shape_get_rectangles_rectangles_length(reply); + const int nRectangles = xcb_shape_get_rectangles_rectangles_length(reply.get()); for (int i = 0; !interacts && i < nRectangles; ++i) { interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos); } } - free(reply); } return interacts; @@ -268,33 +249,25 @@ xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md return 0; if (md) { - xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(xcb_connection(), w); - xcb_get_window_attributes_reply_t *reply = xcb_get_window_attributes_reply(xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_window_attributes, xcb_connection(), w); if (!reply) return 0; if (reply->map_state != XCB_MAP_STATE_VIEWABLE) return 0; - free(reply); - - xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(xcb_connection(), w); - xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(xcb_connection(), gcookie, 0); + auto greply = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), w); if (!greply) return 0; QRect windowRect(greply->x, greply->y, greply->width, greply->height); - free(greply); if (windowRect.contains(pos)) { bool windowContainsMouse = !ignoreNonXdndAwareWindows; { - xcb_get_property_cookie_t cookie = - Q_XCB_CALL(xcb_get_property(xcb_connection(), false, w, connection()->atom(QXcbAtom::XdndAware), - XCB_GET_PROPERTY_TYPE_ANY, 0, 0)); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); - + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), + false, w, connection()->atom(QXcbAtom::XdndAware), + XCB_GET_PROPERTY_TYPE_ANY, 0, 0); bool isAware = reply && reply->type != XCB_NONE; - free(reply); if (isAware) { const QPoint relPos = pos - windowRect.topLeft(); // When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we @@ -310,19 +283,16 @@ xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md } } - xcb_query_tree_cookie_t cookie = xcb_query_tree (xcb_connection(), w); - xcb_query_tree_reply_t *reply = xcb_query_tree_reply(xcb_connection(), cookie, 0); - + auto reply = Q_XCB_REPLY(xcb_query_tree, xcb_connection(), w); if (!reply) return 0; - int nc = xcb_query_tree_children_length(reply); - xcb_window_t *c = xcb_query_tree_children(reply); + int nc = xcb_query_tree_children_length(reply.get()); + xcb_window_t *c = xcb_query_tree_children(reply.get()); xcb_window_t r = 0; for (uint i = nc; !r && i--;) r = findRealWindow(pos - windowRect.topLeft(), c[i], md-1, ignoreNonXdndAwareWindows); - free(reply); if (r) return r; @@ -363,15 +333,14 @@ void QXcbDrag::move(const QPoint &globalPos) } xcb_window_t rootwin = current_virtual_desktop->root(); - xcb_translate_coordinates_reply_t *translate = - ::translateCoordinates(connection(), rootwin, rootwin, globalPos.x(), globalPos.y()); + auto translate = Q_XCB_REPLY(xcb_translate_coordinates, connection()->xcb_connection(), + rootwin, rootwin, globalPos.x(), globalPos.y()); if (!translate) return; xcb_window_t target = translate->child; int lx = translate->dst_x; int ly = translate->dst_y; - free (translate); if (target && target != rootwin) { xcb_window_t src = rootwin; @@ -379,7 +348,8 @@ void QXcbDrag::move(const QPoint &globalPos) DNDDEBUG << "checking target for XdndAware" << target << lx << ly; // translate coordinates - translate = ::translateCoordinates(connection(), src, target, lx, ly); + auto translate = Q_XCB_REPLY(xcb_translate_coordinates, connection()->xcb_connection(), + src, target, lx, ly); if (!translate) { target = 0; break; @@ -388,14 +358,11 @@ void QXcbDrag::move(const QPoint &globalPos) ly = translate->dst_y; src = target; xcb_window_t child = translate->child; - free(translate); // check if it has XdndAware - xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, target, - atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0)); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target, + atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0); bool aware = reply && reply->type != XCB_NONE; - free(reply); if (aware) { DNDDEBUG << "Found XdndAware on " << target; break; @@ -429,16 +396,14 @@ void QXcbDrag::move(const QPoint &globalPos) int target_version = 1; if (proxy_target) { - xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, proxy_target, - atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), + false, proxy_target, + atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1); if (!reply || reply->type == XCB_NONE) target = 0; - target_version = *(uint32_t *)xcb_get_property_value(reply); + target_version = *(uint32_t *)xcb_get_property_value(reply.get()); target_version = qMin(xdnd_version, target_version ? target_version : 1); - - free(reply); } if (target != current_target) { @@ -721,21 +686,19 @@ void QXcbDrag::handleEnter(QPlatformWindow *window, const xcb_client_message_eve if (event->data.data32[1] & 1) { // get the types from XdndTypeList - xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, xdnd_dragsource, - atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM, - 0, xdnd_max_type); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource, + atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM, + 0, xdnd_max_type); if (reply && reply->type != XCB_NONE && reply->format == 32) { - int length = xcb_get_property_value_length(reply) / 4; + int length = xcb_get_property_value_length(reply.get()) / 4; if (length > xdnd_max_type) length = xdnd_max_type; - xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply); + xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get()); xdnd_types.reserve(length); for (int i = 0; i < length; ++i) xdnd_types.append(atoms[i]); } - free(reply); } else { // get the types from the message for(int i = 2; i < 5; i++) { @@ -776,7 +739,7 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message dropData = currentDrag()->mimeData(); supported_actions = currentDrag()->supportedActions(); } else { - dropData = platformDropData(); + dropData = m_dropData; supported_actions = Qt::DropActions(toDropAction(e->data.data32[4])); } @@ -819,8 +782,8 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message handle_xdnd_status(&response); else #endif - Q_XCB_CALL(xcb_send_event(xcb_connection(), false, current_proxy_target, - XCB_EVENT_MASK_NO_EVENT, (const char *)&response)); + xcb_send_event(xcb_connection(), false, current_proxy_target, + XCB_EVENT_MASK_NO_EVENT, (const char *)&response); } namespace @@ -997,7 +960,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e dropData = currentDrag()->mimeData(); supported_drop_actions = Qt::DropActions(l[4]); } else { - dropData = platformDropData(); + dropData = m_dropData; supported_drop_actions = accepted_drop_action; // Drop coming from another app? Update keyboard modifiers. @@ -1024,8 +987,8 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e 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()); - Q_XCB_CALL(xcb_send_event(xcb_connection(), false, current_proxy_target, - XCB_EVENT_MASK_NO_EVENT, (char *)&finished)); + xcb_send_event(xcb_connection(), false, current_proxy_target, + XCB_EVENT_MASK_NO_EVENT, (char *)&finished); xdnd_dragsource = 0; currentWindow.clear(); @@ -1143,28 +1106,20 @@ static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window) xcb_window_t target = 0; forever { // check if window has XdndAware - xcb_get_property_cookie_t gpCookie = Q_XCB_CALL( - xcb_get_property(c->xcb_connection(), false, window, - c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0)); - xcb_get_property_reply_t *gpReply = xcb_get_property_reply( - c->xcb_connection(), gpCookie, 0); + auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window, + c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0); bool aware = gpReply && gpReply->type != XCB_NONE; - free(gpReply); if (aware) { target = window; break; } // try window's parent - xcb_query_tree_cookie_t qtCookie = Q_XCB_CALL( - xcb_query_tree_unchecked(c->xcb_connection(), window)); - xcb_query_tree_reply_t *qtReply = xcb_query_tree_reply( - c->xcb_connection(), qtCookie, NULL); + auto qtReply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, c->xcb_connection(), window); if (!qtReply) break; xcb_window_t root = qtReply->root; xcb_window_t parent = qtReply->parent; - free(qtReply); if (window == root) break; window = parent; diff --git a/src/plugins/platforms/xcb/qxcbdrag.h b/src/plugins/platforms/xcb/qxcbdrag.h index 9e1ee46593..31f1c47d83 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.h +++ b/src/plugins/platforms/xcb/qxcbdrag.h @@ -74,7 +74,6 @@ public: QXcbDrag(QXcbConnection *c); ~QXcbDrag(); - QMimeData *platformDropData() override; bool eventFilter(QObject *o, QEvent *e) override; void startDrag() override; @@ -117,7 +116,7 @@ private: QPointer<QWindow> currentWindow; QPoint currentPosition; - QXcbDropData *dropData; + QXcbDropData *m_dropData; Qt::DropAction accepted_drop_action; QWindow *desktop_proxy; diff --git a/src/plugins/platforms/xcb/qxcbimage.cpp b/src/plugins/platforms/xcb/qxcbimage.cpp index c419bd913d..36536e0602 100644 --- a/src/plugins/platforms/xcb/qxcbimage.cpp +++ b/src/plugins/platforms/xcb/qxcbimage.cpp @@ -101,19 +101,14 @@ QPixmap qt_xcb_pixmapFromXPixmap(QXcbConnection *connection, xcb_pixmap_t pixmap { xcb_connection_t *conn = connection->xcb_connection(); - xcb_get_image_cookie_t get_image_cookie = - xcb_get_image_unchecked(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, - 0, 0, width, height, 0xffffffff); - - xcb_get_image_reply_t *image_reply = - xcb_get_image_reply(conn, get_image_cookie, NULL); - + auto image_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_image, conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, + 0, 0, width, height, 0xffffffff); if (!image_reply) { return QPixmap(); } - uint8_t *data = xcb_get_image_data(image_reply); - uint32_t length = xcb_get_image_data_length(image_reply); + uint8_t *data = xcb_get_image_data(image_reply.get()); + uint32_t length = xcb_get_image_data_length(image_reply.get()); QPixmap result; @@ -176,7 +171,6 @@ QPixmap qt_xcb_pixmapFromXPixmap(QXcbConnection *connection, xcb_pixmap_t pixmap result = QPixmap::fromImage(image.copy()); } - free(image_reply); return result; } @@ -214,22 +208,15 @@ xcb_cursor_t qt_xcb_createCursorXRender(QXcbScreen *screen, const QImage &image, xcb_connection_t *conn = screen->xcb_connection(); const int w = image.width(); const int h = image.height(); - xcb_generic_error_t *error = 0; - xcb_render_query_pict_formats_cookie_t formatsCookie = xcb_render_query_pict_formats(conn); - xcb_render_query_pict_formats_reply_t *formatsReply = xcb_render_query_pict_formats_reply(conn, - formatsCookie, - &error); - if (!formatsReply || error) { + auto formats = Q_XCB_REPLY(xcb_render_query_pict_formats, conn); + if (!formats) { qWarning("qt_xcb_createCursorXRender: query_pict_formats failed"); - free(formatsReply); - free(error); return XCB_NONE; } - xcb_render_pictforminfo_t *fmt = xcb_render_util_find_standard_format(formatsReply, + xcb_render_pictforminfo_t *fmt = xcb_render_util_find_standard_format(formats.get(), XCB_PICT_STANDARD_ARGB_32); if (!fmt) { qWarning("qt_xcb_createCursorXRender: Failed to find format PICT_STANDARD_ARGB_32"); - free(formatsReply); return XCB_NONE; } @@ -241,17 +228,15 @@ xcb_cursor_t qt_xcb_createCursorXRender(QXcbScreen *screen, const QImage &image, 0, 0, 0); if (!xi) { qWarning("qt_xcb_createCursorXRender: xcb_image_create failed"); - free(formatsReply); return XCB_NONE; } xi->data = (uint8_t *) malloc(xi->stride * h); if (!xi->data) { qWarning("qt_xcb_createCursorXRender: Failed to malloc() image data"); xcb_image_destroy(xi); - free(formatsReply); return XCB_NONE; } - memcpy(xi->data, img.constBits(), img.byteCount()); + memcpy(xi->data, img.constBits(), img.sizeInBytes()); xcb_pixmap_t pix = xcb_generate_id(conn); xcb_create_pixmap(conn, 32, pix, screen->root(), w, h); @@ -271,7 +256,6 @@ xcb_cursor_t qt_xcb_createCursorXRender(QXcbScreen *screen, const QImage &image, xcb_image_destroy(xi); xcb_render_free_picture(conn, pic); xcb_free_pixmap(conn, pix); - free(formatsReply); return cursor; #else diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index c9ecdceb0d..72d31060db 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -65,6 +65,11 @@ #if QT_CONFIG(xcb_xlib) #include <X11/Xlib.h> +#if QT_CONFIG(xcb_native_painting) +#include "qxcbnativepainting.h" +#include "qpixmap_x11_p.h" +#include "qbackingstore_x11_p.h" +#endif #endif #include <qpa/qplatforminputcontextfactory_p.h> @@ -83,6 +88,11 @@ #include <QtCore/QFileInfo> +#if QT_CONFIG(vulkan) +#include "qxcbvulkaninstance.h" +#include "qxcbvulkanwindow.h" +#endif + QT_BEGIN_NAMESPACE // Find out if our parent process is gdb by looking at the 'exe' symlink under /proc,. @@ -200,6 +210,13 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char } m_fontDatabase.reset(new QGenericUnixFontDatabase()); + +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) { + qDebug("QXCB USING NATIVE PAINTING"); + qt_xcb_native_x11_info_init(defaultConnection()); + } +#endif } QXcbIntegration::~QXcbIntegration() @@ -208,15 +225,33 @@ QXcbIntegration::~QXcbIntegration() m_instance = Q_NULLPTR; } +QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const +{ +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) + return new QX11PlatformPixmap(type); +#endif + + return QPlatformIntegration::createPlatformPixmap(type); +} + QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const { QXcbScreen *screen = static_cast<QXcbScreen *>(window->screen()->handle()); QXcbGlIntegration *glIntegration = screen->connection()->glIntegration(); - if (window->type() != Qt::Desktop && window->supportsOpenGL()) { - if (glIntegration) { - QXcbWindow *xcbWindow = glIntegration->createWindow(window); + if (window->type() != Qt::Desktop) { + if (window->supportsOpenGL()) { + if (glIntegration) { + QXcbWindow *xcbWindow = glIntegration->createWindow(window); + xcbWindow->create(); + return xcbWindow; + } +#if QT_CONFIG(vulkan) + } else if (window->surfaceType() == QSurface::VulkanSurface) { + QXcbWindow *xcbWindow = new QXcbVulkanWindow(window); xcbWindow->create(); return xcbWindow; +#endif } } @@ -247,6 +282,11 @@ QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLCont QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *window) const { +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) + return new QXcbNativeBackingStore(window); +#endif + return new QXcbBackingStore(window); } @@ -498,4 +538,21 @@ void QXcbIntegration::beep() const xcb_bell(connection, 0); } +bool QXcbIntegration::nativePaintingEnabled() const +{ +#if QT_CONFIG(xcb_native_painting) + static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING"); + return enabled; +#else + return false; +#endif +} + +#if QT_CONFIG(vulkan) +QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const +{ + return new QXcbVulkanInstance(instance); +} +#endif + QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h index b3d72c19d0..186b6c5ddd 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.h +++ b/src/plugins/platforms/xcb/qxcbintegration.h @@ -61,6 +61,7 @@ public: QXcbIntegration(const QStringList ¶meters, int &argc, char **argv); ~QXcbIntegration(); + QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const override; QPlatformWindow *createPlatformWindow(QWindow *window) const override; QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const override; #ifndef QT_NO_OPENGL @@ -114,6 +115,12 @@ public: void beep() const override; + bool nativePaintingEnabled() const; + +#if QT_CONFIG(vulkan) + QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; +#endif + static QXcbIntegration *instance() { return m_instance; } private: diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp index 2e29c208c7..e24bd07b6f 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp +++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp @@ -53,7 +53,7 @@ #include <stdio.h> #include <X11/keysym.h> -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) #include <X11/extensions/XI2proto.h> #undef KeyPress #undef KeyRelease @@ -612,23 +612,18 @@ Qt::KeyboardModifiers QXcbKeyboard::translateModifiers(int s) const void QXcbKeyboard::readXKBConfig() { clearXKBConfig(); - xcb_generic_error_t *error; - xcb_get_property_cookie_t cookie; - xcb_get_property_reply_t *config_reply; xcb_connection_t *c = xcb_connection(); xcb_window_t rootWindow = connection()->rootWindow(); - cookie = xcb_get_property(c, 0, rootWindow, - atom(QXcbAtom::_XKB_RULES_NAMES), XCB_ATOM_STRING, 0, 1024); - - config_reply = xcb_get_property_reply(c, cookie, &error); + auto config_reply = Q_XCB_REPLY(xcb_get_property, c, 0, rootWindow, + atom(QXcbAtom::_XKB_RULES_NAMES), XCB_ATOM_STRING, 0, 1024); if (!config_reply) { qWarning("Qt: Couldn't interpret the _XKB_RULES_NAMES property"); return; } - char *xkb_config = (char *)xcb_get_property_value(config_reply); - int length = xcb_get_property_value_length(config_reply); + char *xkb_config = (char *)xcb_get_property_value(config_reply.get()); + int length = xcb_get_property_value_length(config_reply.get()); // on old X servers xkb_config can be 0 even if config_reply indicates a succesfull read if (!xkb_config || length == 0) @@ -653,8 +648,6 @@ void QXcbKeyboard::readXKBConfig() xkb_names.layout = qstrdup(names[2]); xkb_names.variant = qstrdup(names[3]); xkb_names.options = qstrdup(names[4]); - - free(config_reply); } void QXcbKeyboard::clearXKBConfig() @@ -807,7 +800,7 @@ void QXcbKeyboard::updateXKBStateFromCore(quint16 state) } } -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) void QXcbKeyboard::updateXKBStateFromXI(void *modInfo, void *groupInfo) { if (m_config && !connection()->hasXKB()) { @@ -1172,23 +1165,19 @@ QXcbKeyboard::~QXcbKeyboard() void QXcbKeyboard::updateVModMapping() { #if QT_CONFIG(xkb) - xcb_xkb_get_names_cookie_t names_cookie; - xcb_xkb_get_names_reply_t *name_reply; xcb_xkb_get_names_value_list_t names_list; memset(&vmod_masks, 0, sizeof(vmod_masks)); - names_cookie = xcb_xkb_get_names(xcb_connection(), - XCB_XKB_ID_USE_CORE_KBD, - XCB_XKB_NAME_DETAIL_VIRTUAL_MOD_NAMES); - - name_reply = xcb_xkb_get_names_reply(xcb_connection(), names_cookie, 0); + auto name_reply = Q_XCB_REPLY(xcb_xkb_get_names, xcb_connection(), + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_NAME_DETAIL_VIRTUAL_MOD_NAMES); if (!name_reply) { qWarning("Qt: failed to retrieve the virtual modifier names from XKB"); return; } - const void *buffer = xcb_xkb_get_names_value_list(name_reply); + const void *buffer = xcb_xkb_get_names_value_list(name_reply.get()); xcb_xkb_get_names_value_list_unpack(buffer, name_reply->nTypes, name_reply->indicators, @@ -1233,32 +1222,27 @@ void QXcbKeyboard::updateVModMapping() else if (qstrcmp(vmod_name, "Hyper") == 0) vmod_masks.hyper = bit; } - - free(name_reply); #endif } void QXcbKeyboard::updateVModToRModMapping() { #if QT_CONFIG(xkb) - xcb_xkb_get_map_cookie_t map_cookie; - xcb_xkb_get_map_reply_t *map_reply; xcb_xkb_get_map_map_t map; memset(&rmod_masks, 0, sizeof(rmod_masks)); - map_cookie = xcb_xkb_get_map(xcb_connection(), - XCB_XKB_ID_USE_CORE_KBD, - XCB_XKB_MAP_PART_VIRTUAL_MODS, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - - map_reply = xcb_xkb_get_map_reply(xcb_connection(), map_cookie, 0); + auto map_reply = Q_XCB_REPLY(xcb_xkb_get_map, + xcb_connection(), + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_MAP_PART_VIRTUAL_MODS, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (!map_reply) { qWarning("Qt: failed to retrieve the virtual modifier map from XKB"); return; } - const void *buffer = xcb_xkb_get_map_map(map_reply); + const void *buffer = xcb_xkb_get_map_map(map_reply.get()); xcb_xkb_get_map_map_unpack(buffer, map_reply->nTypes, map_reply->nKeySyms, @@ -1301,7 +1285,6 @@ void QXcbKeyboard::updateVModToRModMapping() rmod_masks.hyper = modmap; } - free(map_reply); resolveMaskConflicts(); #endif } @@ -1315,14 +1298,10 @@ void QXcbKeyboard::updateModifiers() // process for all modifiers whenever any part of the modifier mapping is changed. memset(&rmod_masks, 0, sizeof(rmod_masks)); - xcb_generic_error_t *error = 0; xcb_connection_t *conn = xcb_connection(); - xcb_get_modifier_mapping_cookie_t modMapCookie = xcb_get_modifier_mapping(conn); - xcb_get_modifier_mapping_reply_t *modMapReply = - xcb_get_modifier_mapping_reply(conn, modMapCookie, &error); - if (error) { + auto modMapReply = Q_XCB_REPLY(xcb_get_modifier_mapping, conn); + if (!modMapReply) { qWarning("Qt: failed to get modifier mapping"); - free(error); return; } @@ -1338,7 +1317,7 @@ void QXcbKeyboard::updateModifiers() for (size_t i = 0; i < numSymbols; ++i) modKeyCodes[i] = xcb_key_symbols_get_keycode(m_key_symbols, symbols[i]); - xcb_keycode_t *modMap = xcb_get_modifier_mapping_keycodes(modMapReply); + xcb_keycode_t *modMap = xcb_get_modifier_mapping_keycodes(modMapReply.get()); const int w = modMapReply->keycodes_per_modifier; for (size_t i = 0; i < numSymbols; ++i) { for (int bit = 0; bit < 8; ++bit) { @@ -1366,7 +1345,6 @@ void QXcbKeyboard::updateModifiers() for (size_t i = 0; i < numSymbols; ++i) free(modKeyCodes[i]); - free(modMapReply); resolveMaskConflicts(); } @@ -1449,8 +1427,6 @@ private: void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, xcb_keycode_t code, quint16 state, xcb_timestamp_t time) { - Q_XCB_NOOP(connection()); - if (!m_config) return; @@ -1471,30 +1447,6 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, updateXKBStateFromState(kb_state, state); xcb_keysym_t sym = xkb_state_key_get_one_sym(kb_state, code); - - QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); - QMetaMethod method; - - if (inputContext) { - int methodIndex = inputContext->metaObject()->indexOfMethod("x11FilterEvent(uint,uint,uint,bool)"); - if (methodIndex != -1) - method = inputContext->metaObject()->method(methodIndex); - } - - if (method.isValid()) { - bool retval = false; - method.invoke(inputContext, Qt::DirectConnection, - Q_RETURN_ARG(bool, retval), - Q_ARG(uint, sym), - Q_ARG(uint, code), - Q_ARG(uint, state), - Q_ARG(bool, type == QEvent::KeyPress)); - if (retval) { - xkb_state_unref(kb_state); - return; - } - } - QString string = lookupString(kb_state, code); // Ιf control modifier is set we should prefer latin character, this is @@ -1526,6 +1478,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, } bool filtered = false; + QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); if (inputContext) { QKeyEvent event(type, qtcode, modifiers, code, sym, state, string, isAutoRepeat, string.length()); event.setTimestamp(time); @@ -1548,16 +1501,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, if (isAutoRepeat && type == QEvent::KeyRelease) { // since we removed it from the event queue using checkEvent we need to send the key press here filtered = false; - if (method.isValid()) { - method.invoke(inputContext, Qt::DirectConnection, - Q_RETURN_ARG(bool, filtered), - Q_ARG(uint, sym), - Q_ARG(uint, code), - Q_ARG(uint, state), - Q_ARG(bool, true)); - } - - if (!filtered && inputContext) { + if (inputContext) { QKeyEvent event(QEvent::KeyPress, qtcode, modifiers, code, sym, state, string, isAutoRepeat, string.length()); event.setTimestamp(time); filtered = inputContext->filterEvent(&event); diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.h b/src/plugins/platforms/xcb/qxcbkeyboard.h index 74f9da0353..7f1c51fab8 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.h +++ b/src/plugins/platforms/xcb/qxcbkeyboard.h @@ -74,7 +74,7 @@ public: void updateXKBMods(); quint32 xkbModMask(quint16 state); void updateXKBStateFromCore(quint16 state); -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) void updateXKBStateFromXI(void *modInfo, void *groupInfo); #endif #if QT_CONFIG(xkb) diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp index 97dcb8f328..caa9499c45 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp @@ -63,6 +63,10 @@ #include "qxcbnativeinterfacehandler.h" +#if QT_CONFIG(vulkan) +#include "qxcbvulkanwindow.h" +#endif + QT_BEGIN_NAMESPACE // return QXcbNativeInterface::ResourceType for the key. @@ -78,7 +82,11 @@ static int resourceType(const QByteArray &key) QByteArrayLiteral("rootwindow"), QByteArrayLiteral("subpixeltype"), QByteArrayLiteral("antialiasingenabled"), QByteArrayLiteral("atspibus"), - QByteArrayLiteral("compositingenabled") + QByteArrayLiteral("compositingenabled"), + QByteArrayLiteral("vksurface"), + QByteArrayLiteral("generatepeekerid"), + QByteArrayLiteral("removepeekerid"), + QByteArrayLiteral("peekeventqueue") }; const QByteArray *end = names + sizeof(names) / sizeof(names[0]); const QByteArray *result = std::find(names, end, key); @@ -117,28 +125,19 @@ xcb_window_t QXcbNativeInterface::locateSystemTray(xcb_connection_t *conn, const { if (m_sysTraySelectionAtom == XCB_ATOM_NONE) { const QByteArray net_sys_tray = QString::fromLatin1("_NET_SYSTEM_TRAY_S%1").arg(screen->screenNumber()).toLatin1(); - xcb_intern_atom_cookie_t intern_c = - xcb_intern_atom_unchecked(conn, true, net_sys_tray.length(), net_sys_tray); - - xcb_intern_atom_reply_t *intern_r = xcb_intern_atom_reply(conn, intern_c, 0); - + auto intern_r = Q_XCB_REPLY_UNCHECKED(xcb_intern_atom, conn, + true, net_sys_tray.length(), net_sys_tray); if (!intern_r) return XCB_WINDOW_NONE; m_sysTraySelectionAtom = intern_r->atom; - free(intern_r); } - xcb_get_selection_owner_cookie_t sel_owner_c = xcb_get_selection_owner_unchecked(conn, m_sysTraySelectionAtom); - xcb_get_selection_owner_reply_t *sel_owner_r = xcb_get_selection_owner_reply(conn, sel_owner_c, 0); - + auto sel_owner_r = Q_XCB_REPLY_UNCHECKED(xcb_get_selection_owner, conn, m_sysTraySelectionAtom); if (!sel_owner_r) return XCB_WINDOW_NONE; - xcb_window_t selection_window = sel_owner_r->owner; - free(sel_owner_r); - - return selection_window; + return sel_owner_r->owner; } bool QXcbNativeInterface::systrayVisualHasAlphaChannel() @@ -262,6 +261,14 @@ void *QXcbNativeInterface::nativeResourceForWindow(const QByteArray &resourceStr case Screen: result = screenForWindow(window); break; +#if QT_CONFIG(vulkan) + case VkSurface: + if (window->surfaceType() == QSurface::VulkanSurface && window->handle()) { + // return a pointer to the VkSurfaceKHR value, not the value itself + result = static_cast<QXcbVulkanWindow *>(window->handle())->surface(); + } + break; +#endif default: break; } @@ -300,6 +307,13 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QXcbNativeInterfa if (lowerCaseResource == "setstartupid") return NativeResourceForIntegrationFunction(setStartupId); + if (lowerCaseResource == "generatepeekerid") + return NativeResourceForIntegrationFunction(generatePeekerId); + if (lowerCaseResource == "removepeekerid") + return NativeResourceForIntegrationFunction(removePeekerId); + if (lowerCaseResource == "peekeventqueue") + return NativeResourceForIntegrationFunction(peekEventQueue); + return 0; } @@ -453,21 +467,13 @@ void *QXcbNativeInterface::atspiBus() QXcbConnection *defaultConnection = integration->defaultConnection(); if (defaultConnection) { xcb_atom_t atspiBusAtom = defaultConnection->internAtom("AT_SPI_BUS"); - xcb_get_property_cookie_t cookie = Q_XCB_CALL2(xcb_get_property( - defaultConnection->xcb_connection(), - false, defaultConnection->rootWindow(), - atspiBusAtom, XCB_ATOM_STRING, 0, 128), - defaultConnection); - xcb_get_property_reply_t *reply = Q_XCB_CALL2(xcb_get_property_reply( - defaultConnection->xcb_connection(), - cookie, 0), - defaultConnection); + auto reply = Q_XCB_REPLY(xcb_get_property, defaultConnection->xcb_connection(), + false, defaultConnection->rootWindow(), + atspiBusAtom, XCB_ATOM_STRING, 0, 128); Q_ASSERT(!reply->bytes_after); - char *data = (char *)xcb_get_property_value(reply); - int length = xcb_get_property_value_length(reply); - QByteArray *busAddress = new QByteArray(data, length); - free(reply); - return busAddress; + char *data = (char *)xcb_get_property_value(reply.get()); + int length = xcb_get_property_value_length(reply.get()); + return new QByteArray(data, length); } return 0; } @@ -486,6 +492,25 @@ void QXcbNativeInterface::setAppUserTime(QScreen* screen, xcb_timestamp_t time) } } +qint32 QXcbNativeInterface::generatePeekerId() +{ + QXcbIntegration *integration = QXcbIntegration::instance(); + return integration->defaultConnection()->generatePeekerId(); +} + +bool QXcbNativeInterface::removePeekerId(qint32 peekerId) +{ + QXcbIntegration *integration = QXcbIntegration::instance(); + return integration->defaultConnection()->removePeekerId(peekerId); +} + +bool QXcbNativeInterface::peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData, + QXcbConnection::PeekOptions option, qint32 peekerId) +{ + QXcbIntegration *integration = QXcbIntegration::instance(); + return integration->defaultConnection()->peekEventQueue(peeker, peekerData, option, peekerId); +} + void QXcbNativeInterface::setStartupId(const char *data) { QByteArray startupId(data); diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h index 4186d77f4d..fb0db727aa 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.h +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.h @@ -46,12 +46,11 @@ #include <QtCore/QRect> #include "qxcbexport.h" +#include "qxcbconnection.h" QT_BEGIN_NAMESPACE -class QWidget; class QXcbScreen; -class QXcbConnection; class QXcbNativeInterfaceHandler; class Q_XCB_EXPORT QXcbNativeInterface : public QPlatformNativeInterface @@ -73,7 +72,11 @@ public: ScreenSubpixelType, ScreenAntialiasingEnabled, AtspiBus, - CompositingEnabled + CompositingEnabled, + VkSurface, + GeneratePeekerId, + RemovePeekerId, + PeekEventQueue }; QXcbNativeInterface(); @@ -113,6 +116,12 @@ public: static void setAppTime(QScreen *screen, xcb_timestamp_t time); static void setAppUserTime(QScreen *screen, xcb_timestamp_t time); + static qint32 generatePeekerId(); + static bool removePeekerId(qint32 peekerId); + static bool peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData = nullptr, + QXcbConnection::PeekOptions option = QXcbConnection::PeekDefault, + qint32 peekerId = -1); + Q_INVOKABLE bool systemTrayAvailable(const QScreen *screen) const; Q_INVOKABLE void setParentRelativeBackPixmap(QWindow *window); Q_INVOKABLE bool systrayVisualHasAlphaChannel(); diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp index 5e136b5d7e..67c96b2d80 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.cpp +++ b/src/plugins/platforms/xcb/qxcbscreen.cpp @@ -65,6 +65,67 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t m_compositingActive = connection->getSelectionOwner(m_net_wm_cm_atom); m_workArea = getWorkArea(); + + readXResources(); + + auto rootAttribs = Q_XCB_REPLY_UNCHECKED(xcb_get_window_attributes, xcb_connection(), + screen->root); + const quint32 existingEventMask = !rootAttribs ? 0 : rootAttribs->your_event_mask; + + const quint32 mask = XCB_CW_EVENT_MASK; + const quint32 values[] = { + // XCB_CW_EVENT_MASK + XCB_EVENT_MASK_ENTER_WINDOW + | XCB_EVENT_MASK_LEAVE_WINDOW + | XCB_EVENT_MASK_PROPERTY_CHANGE + | XCB_EVENT_MASK_STRUCTURE_NOTIFY // for the "MANAGER" atom (system tray notification). + | existingEventMask // don't overwrite the event mask on the root window + }; + + xcb_change_window_attributes(xcb_connection(), screen->root, mask, values); + + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), + false, screen->root, + atom(QXcbAtom::_NET_SUPPORTING_WM_CHECK), + XCB_ATOM_WINDOW, 0, 1024); + if (reply && reply->format == 32 && reply->type == XCB_ATOM_WINDOW) { + xcb_window_t windowManager = *((xcb_window_t *)xcb_get_property_value(reply.get())); + + if (windowManager != XCB_WINDOW_NONE) { + auto windowManagerReply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), + false, windowManager, + atom(QXcbAtom::_NET_WM_NAME), + atom(QXcbAtom::UTF8_STRING), 0, 1024); + if (windowManagerReply && windowManagerReply->format == 8 && windowManagerReply->type == atom(QXcbAtom::UTF8_STRING)) { + m_windowManagerName = QString::fromUtf8((const char *)xcb_get_property_value(windowManagerReply.get()), + xcb_get_property_value_length(windowManagerReply.get())); + } + } + } + + const xcb_query_extension_reply_t *sync_reply = xcb_get_extension_data(xcb_connection(), &xcb_sync_id); + if (!sync_reply || !sync_reply->present) + m_syncRequestSupported = false; + else + m_syncRequestSupported = true; + + xcb_depth_iterator_t depth_iterator = + xcb_screen_allowed_depths_iterator(screen); + + while (depth_iterator.rem) { + xcb_depth_t *depth = depth_iterator.data; + xcb_visualtype_iterator_t visualtype_iterator = + xcb_depth_visuals_iterator(depth); + + while (visualtype_iterator.rem) { + xcb_visualtype_t *visualtype = visualtype_iterator.data; + m_visuals.insert(visualtype->visual_id, *visualtype); + m_visualDepths.insert(visualtype->visual_id, depth->depth); + xcb_visualtype_next(&visualtype_iterator); + } + + xcb_depth_next(&depth_iterator); + } } QXcbVirtualDesktop::~QXcbVirtualDesktop() @@ -123,18 +184,33 @@ void QXcbVirtualDesktop::subscribeToXFixesSelectionNotify() const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; - Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->getQtSelectionOwner(), m_net_wm_cm_atom, mask)); + xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->getQtSelectionOwner(), m_net_wm_cm_atom, mask); } } +/*! \internal + + Using _NET_WORKAREA to calculate the available desktop geometry on multi-head systems (systems + with more than one monitor) is unreliable. Different WMs have different interpretations of what + _NET_WORKAREA means with multiple attached monitors. This gets worse when monitors have + different dimensions and/or screens are not virtually aligned. In Qt we want the available + geometry per monitor (QScreen), not desktop (represented by _NET_WORKAREA). WM specification + does not have an atom for this. Thus, QScreen is limted by the lack of support from the + underlying system. + + One option could be that Qt does WM's job of calculating this by subtracting geometries of + _NET_WM_STRUT_PARTIAL and windows where _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_DOCK. + But this won't work on Gnome 3 shell as it seems that on this desktop environment the tool panel + is painted directly on the root window. Maybe there is some Gnome/GTK API that could be used + to get height of the panel, but I did not find one. Maybe other WMs have their own tricks, so + the reliability of this approach is questionable. + */ QRect QXcbVirtualDesktop::getWorkArea() const { QRect r; - xcb_get_property_reply_t * workArea = - xcb_get_property_reply(xcb_connection(), - xcb_get_property_unchecked(xcb_connection(), false, screen()->root, - atom(QXcbAtom::_NET_WORKAREA), - XCB_ATOM_CARDINAL, 0, 1024), NULL); + auto workArea = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), false, screen()->root, + atom(QXcbAtom::_NET_WORKAREA), + XCB_ATOM_CARDINAL, 0, 1024); if (workArea && workArea->type == XCB_ATOM_CARDINAL && workArea->format == 32 && workArea->value_len >= 4) { // If workArea->value_len > 4, the remaining ones seem to be for WM's virtual desktops // (don't mess with QXcbVirtualDesktop which represents an X screen). @@ -142,12 +218,11 @@ QRect QXcbVirtualDesktop::getWorkArea() const // "docked" panel (with _NET_WM_STRUT_PARTIAL atom set) on just one desktop. // But for now just assume the first 4 values give us the geometry of the // "work area", AKA "available geometry" - uint32_t *geom = (uint32_t*)xcb_get_property_value(workArea); + uint32_t *geom = (uint32_t*)xcb_get_property_value(workArea.get()); r = QRect(geom[0], geom[1], geom[2], geom[3]); } else { r = QRect(QPoint(), size()); } - free(workArea); return r; } @@ -167,6 +242,170 @@ static inline QSizeF sizeInMillimeters(const QSize &size, const QDpi &dpi) Q_MM_PER_INCH * size.height() / dpi.second); } +bool QXcbVirtualDesktop::xResource(const QByteArray &identifier, + const QByteArray &expectedIdentifier, + QByteArray& stringValue) +{ + if (identifier.startsWith(expectedIdentifier)) { + stringValue = identifier.mid(expectedIdentifier.size()); + return true; + } + return false; +} + +static bool parseXftInt(const QByteArray& stringValue, int *value) +{ + Q_ASSERT(value != 0); + bool ok; + *value = stringValue.toInt(&ok); + return ok; +} + +static QFontEngine::HintStyle parseXftHintStyle(const QByteArray& stringValue) +{ + if (stringValue == "hintfull") + return QFontEngine::HintFull; + else if (stringValue == "hintnone") + return QFontEngine::HintNone; + else if (stringValue == "hintmedium") + return QFontEngine::HintMedium; + else if (stringValue == "hintslight") + return QFontEngine::HintLight; + + return QFontEngine::HintStyle(-1); +} + +static QFontEngine::SubpixelAntialiasingType parseXftRgba(const QByteArray& stringValue) +{ + if (stringValue == "none") + return QFontEngine::Subpixel_None; + else if (stringValue == "rgb") + return QFontEngine::Subpixel_RGB; + else if (stringValue == "bgr") + return QFontEngine::Subpixel_BGR; + else if (stringValue == "vrgb") + return QFontEngine::Subpixel_VRGB; + else if (stringValue == "vbgr") + return QFontEngine::Subpixel_VBGR; + + return QFontEngine::SubpixelAntialiasingType(-1); +} + +void QXcbVirtualDesktop::readXResources() +{ + int offset = 0; + QByteArray resources; + while (true) { + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), + false, screen()->root, + XCB_ATOM_RESOURCE_MANAGER, + XCB_ATOM_STRING, offset/4, 8192); + bool more = false; + if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) { + resources += QByteArray((const char *)xcb_get_property_value(reply.get()), xcb_get_property_value_length(reply.get())); + offset += xcb_get_property_value_length(reply.get()); + more = reply->bytes_after != 0; + } + + if (!more) + break; + } + + QList<QByteArray> split = resources.split('\n'); + for (int i = 0; i < split.size(); ++i) { + const QByteArray &r = split.at(i); + int value; + QByteArray stringValue; + if (xResource(r, "Xft.dpi:\t", stringValue)) { + if (parseXftInt(stringValue, &value)) + m_forcedDpi = value; + } else if (xResource(r, "Xft.hintstyle:\t", stringValue)) { + m_hintStyle = parseXftHintStyle(stringValue); + } else if (xResource(r, "Xft.antialias:\t", stringValue)) { + if (parseXftInt(stringValue, &value)) + m_antialiasingEnabled = value; + } else if (xResource(r, "Xft.rgba:\t", stringValue)) { + m_subpixelType = parseXftRgba(stringValue); + } + } +} + +QSurfaceFormat QXcbVirtualDesktop::surfaceFormatFor(const QSurfaceFormat &format) const +{ + const xcb_visualid_t xcb_visualid = connection()->hasDefaultVisualId() ? connection()->defaultVisualId() + : screen()->root_visual; + const xcb_visualtype_t *xcb_visualtype = visualForId(xcb_visualid); + + const int redSize = qPopulationCount(xcb_visualtype->red_mask); + const int greenSize = qPopulationCount(xcb_visualtype->green_mask); + const int blueSize = qPopulationCount(xcb_visualtype->blue_mask); + + QSurfaceFormat result = format; + + if (result.redBufferSize() < 0) + result.setRedBufferSize(redSize); + + if (result.greenBufferSize() < 0) + result.setGreenBufferSize(greenSize); + + if (result.blueBufferSize() < 0) + result.setBlueBufferSize(blueSize); + + return result; +} + +const xcb_visualtype_t *QXcbVirtualDesktop::visualForFormat(const QSurfaceFormat &format) const +{ + const xcb_visualtype_t *candidate = nullptr; + + for (const xcb_visualtype_t &xcb_visualtype : m_visuals) { + + const int redSize = qPopulationCount(xcb_visualtype.red_mask); + const int greenSize = qPopulationCount(xcb_visualtype.green_mask); + const int blueSize = qPopulationCount(xcb_visualtype.blue_mask); + const int alphaSize = depthOfVisual(xcb_visualtype.visual_id) - redSize - greenSize - blueSize; + + if (format.redBufferSize() != -1 && redSize != format.redBufferSize()) + continue; + + if (format.greenBufferSize() != -1 && greenSize != format.greenBufferSize()) + continue; + + if (format.blueBufferSize() != -1 && blueSize != format.blueBufferSize()) + continue; + + if (format.alphaBufferSize() != -1 && alphaSize != format.alphaBufferSize()) + continue; + + // Try to find a RGB visual rather than e.g. BGR or GBR + if (qCountTrailingZeroBits(xcb_visualtype.blue_mask) == 0) + return &xcb_visualtype; + + // In case we do not find anything we like, just remember the first one + // and hope for the best: + if (!candidate) + candidate = &xcb_visualtype; + } + + return candidate; +} + +const xcb_visualtype_t *QXcbVirtualDesktop::visualForId(xcb_visualid_t visualid) const +{ + QMap<xcb_visualid_t, xcb_visualtype_t>::const_iterator it = m_visuals.find(visualid); + if (it == m_visuals.constEnd()) + return 0; + return &*it; +} + +quint8 QXcbVirtualDesktop::depthOfVisual(xcb_visualid_t visualid) const +{ + QMap<xcb_visualid_t, quint8>::const_iterator it = m_visualDepths.find(visualid); + if (it == m_visualDepths.constEnd()) + return 0; + return *it; +} + QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop, xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output, const xcb_xinerama_screen_info_t *xineramaScreenInfo, int xineramaScreenIdx) @@ -181,14 +420,11 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDe { if (connection->hasXRandr()) { xcb_randr_select_input(xcb_connection(), screen()->root, true); - xcb_randr_get_crtc_info_cookie_t crtcCookie = - xcb_randr_get_crtc_info_unchecked(xcb_connection(), m_crtc, output ? output->timestamp : 0); - xcb_randr_get_crtc_info_reply_t *crtc = - xcb_randr_get_crtc_info_reply(xcb_connection(), crtcCookie, NULL); + auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(), + m_crtc, output ? output->timestamp : 0); if (crtc) { updateGeometry(QRect(crtc->x, crtc->y, crtc->width, crtc->height), crtc->rotation); updateRefreshRate(crtc->mode); - free(crtc); } } else if (xineramaScreenInfo) { m_geometry = QRect(xineramaScreenInfo->x_org, xineramaScreenInfo->y_org, @@ -208,74 +444,26 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDe if (m_sizeMillimeters.isEmpty()) m_sizeMillimeters = m_virtualSizeMillimeters; - readXResources(); - - QScopedPointer<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> rootAttribs( - xcb_get_window_attributes_reply(xcb_connection(), - xcb_get_window_attributes_unchecked(xcb_connection(), screen()->root), NULL)); - const quint32 existingEventMask = rootAttribs.isNull() ? 0 : rootAttribs->your_event_mask; - - const quint32 mask = XCB_CW_EVENT_MASK; - const quint32 values[] = { - // XCB_CW_EVENT_MASK - XCB_EVENT_MASK_ENTER_WINDOW - | XCB_EVENT_MASK_LEAVE_WINDOW - | XCB_EVENT_MASK_PROPERTY_CHANGE - | XCB_EVENT_MASK_STRUCTURE_NOTIFY // for the "MANAGER" atom (system tray notification). - | existingEventMask // don't overwrite the event mask on the root window - }; - - xcb_change_window_attributes(xcb_connection(), screen()->root, mask, values); - - xcb_get_property_reply_t *reply = - xcb_get_property_reply(xcb_connection(), - xcb_get_property_unchecked(xcb_connection(), false, screen()->root, - atom(QXcbAtom::_NET_SUPPORTING_WM_CHECK), - XCB_ATOM_WINDOW, 0, 1024), NULL); - - if (reply && reply->format == 32 && reply->type == XCB_ATOM_WINDOW) { - xcb_window_t windowManager = *((xcb_window_t *)xcb_get_property_value(reply)); - - if (windowManager != XCB_WINDOW_NONE) { - xcb_get_property_reply_t *windowManagerReply = - xcb_get_property_reply(xcb_connection(), - xcb_get_property_unchecked(xcb_connection(), false, windowManager, - atom(QXcbAtom::_NET_WM_NAME), - atom(QXcbAtom::UTF8_STRING), 0, 1024), NULL); - if (windowManagerReply && windowManagerReply->format == 8 && windowManagerReply->type == atom(QXcbAtom::UTF8_STRING)) { - m_windowManagerName = QString::fromUtf8((const char *)xcb_get_property_value(windowManagerReply), xcb_get_property_value_length(windowManagerReply)); - } - - free(windowManagerReply); - } - } - free(reply); - - const xcb_query_extension_reply_t *sync_reply = xcb_get_extension_data(xcb_connection(), &xcb_sync_id); - if (!sync_reply || !sync_reply->present) - m_syncRequestSupported = false; - else - m_syncRequestSupported = true; - - xcb_depth_iterator_t depth_iterator = - xcb_screen_allowed_depths_iterator(screen()); - - while (depth_iterator.rem) { - xcb_depth_t *depth = depth_iterator.data; - xcb_visualtype_iterator_t visualtype_iterator = - xcb_depth_visuals_iterator(depth); + m_cursor = new QXcbCursor(connection, this); - while (visualtype_iterator.rem) { - xcb_visualtype_t *visualtype = visualtype_iterator.data; - m_visuals.insert(visualtype->visual_id, *visualtype); - m_visualDepths.insert(visualtype->visual_id, depth->depth); - xcb_visualtype_next(&visualtype_iterator); + if (connection->hasXRandr()) { // Parse EDID + QByteArray edid = getEdid(); + if (m_edid.parse(edid)) { + qCDebug(lcQpaScreen, "EDID data for output \"%s\": identifier '%s', manufacturer '%s'," + "model '%s', serial '%s', physical size: %.2fx%.2f", + name().toLatin1().constData(), + m_edid.identifier.toLatin1().constData(), + m_edid.manufacturer.toLatin1().constData(), + m_edid.model.toLatin1().constData(), + m_edid.serialNumber.toLatin1().constData(), + m_edid.physicalSize.width(), m_edid.physicalSize.height()); + } else { + // This property is defined by the xrandr spec. Parsing failure indicates a valid error, + // but keep this as debug, for details see 4f515815efc318ddc909a0399b71b8a684962f38. + qCDebug(lcQpaScreen) << "Failed to parse EDID data for output" << name() << + "edid data: " << edid; } - - xcb_depth_next(&depth_iterator); } - - m_cursor = new QXcbCursor(connection, this); } QXcbScreen::~QXcbScreen() @@ -300,6 +488,21 @@ QString QXcbScreen::getOutputName(xcb_randr_get_output_info_reply_t *outputInfo) return name; } +QString QXcbScreen::manufacturer() const +{ + return m_edid.manufacturer; +} + +QString QXcbScreen::model() const +{ + return m_edid.model; +} + +QString QXcbScreen::serialNumber() const +{ + return m_edid.serialNumber; +} + QWindow *QXcbScreen::topLevelAt(const QPoint &p) const { xcb_window_t root = screen()->root; @@ -311,12 +514,7 @@ QWindow *QXcbScreen::topLevelAt(const QPoint &p) const xcb_window_t child = root; do { - xcb_translate_coordinates_cookie_t translate_cookie = - xcb_translate_coordinates_unchecked(xcb_connection(), parent, child, x, y); - - xcb_translate_coordinates_reply_t *translate_reply = - xcb_translate_coordinates_reply(xcb_connection(), translate_cookie, NULL); - + auto translate_reply = Q_XCB_REPLY_UNCHECKED(xcb_translate_coordinates, xcb_connection(), parent, child, x, y); if (!translate_reply) { return 0; } @@ -326,8 +524,6 @@ QWindow *QXcbScreen::topLevelAt(const QPoint &p) const x = translate_reply->dst_x; y = translate_reply->dst_y; - free(translate_reply); - if (!child || child == root) return 0; @@ -350,62 +546,12 @@ void QXcbScreen::windowShown(QXcbWindow *window) QSurfaceFormat QXcbScreen::surfaceFormatFor(const QSurfaceFormat &format) const { - const xcb_visualid_t xcb_visualid = connection()->hasDefaultVisualId() ? connection()->defaultVisualId() - : screen()->root_visual; - const xcb_visualtype_t *xcb_visualtype = visualForId(xcb_visualid); - - const int redSize = qPopulationCount(xcb_visualtype->red_mask); - const int greenSize = qPopulationCount(xcb_visualtype->green_mask); - const int blueSize = qPopulationCount(xcb_visualtype->blue_mask); - - QSurfaceFormat result = format; - - if (result.redBufferSize() < 0) - result.setRedBufferSize(redSize); - - if (result.greenBufferSize() < 0) - result.setGreenBufferSize(greenSize); - - if (result.blueBufferSize() < 0) - result.setBlueBufferSize(blueSize); - - return result; + return m_virtualDesktop->surfaceFormatFor(format); } -const xcb_visualtype_t *QXcbScreen::visualForFormat(const QSurfaceFormat &format) const +const xcb_visualtype_t *QXcbScreen::visualForId(xcb_visualid_t visualid) const { - const xcb_visualtype_t *candidate = nullptr; - - for (const xcb_visualtype_t &xcb_visualtype : m_visuals) { - - const int redSize = qPopulationCount(xcb_visualtype.red_mask); - const int greenSize = qPopulationCount(xcb_visualtype.green_mask); - const int blueSize = qPopulationCount(xcb_visualtype.blue_mask); - const int alphaSize = depthOfVisual(xcb_visualtype.visual_id) - redSize - greenSize - blueSize; - - if (format.redBufferSize() != -1 && redSize != format.redBufferSize()) - continue; - - if (format.greenBufferSize() != -1 && greenSize != format.greenBufferSize()) - continue; - - if (format.blueBufferSize() != -1 && blueSize != format.blueBufferSize()) - continue; - - if (format.alphaBufferSize() != -1 && alphaSize != format.alphaBufferSize()) - continue; - - // Try to find a RGB visual rather than e.g. BGR or GBR - if (qCountTrailingZeroBits(xcb_visualtype.blue_mask) == 0) - return &xcb_visualtype; - - // In case we do not find anything we like, just remember the first one - // and hope for the best: - if (!candidate) - candidate = &xcb_visualtype; - } - - return candidate; + return m_virtualDesktop->visualForId(visualid); } void QXcbScreen::sendStartupMessage(const QByteArray &message) const @@ -434,20 +580,12 @@ void QXcbScreen::sendStartupMessage(const QByteArray &message) const } while (sent < length); } -const xcb_visualtype_t *QXcbScreen::visualForId(xcb_visualid_t visualid) const +QRect QXcbScreen::availableGeometry() const { - QMap<xcb_visualid_t, xcb_visualtype_t>::const_iterator it = m_visuals.find(visualid); - if (it == m_visuals.constEnd()) - return 0; - return &*it; -} - -quint8 QXcbScreen::depthOfVisual(xcb_visualid_t visualid) const -{ - QMap<xcb_visualid_t, quint8>::const_iterator it = m_visualDepths.find(visualid); - if (it == m_visualDepths.constEnd()) - return 0; - return *it; + static bool enforceNetWorkarea = !qEnvironmentVariableIsEmpty("QT_RELY_ON_NET_WORKAREA_ATOM"); + bool isMultiHeadSystem = virtualSiblings().length() > 1; + bool useScreenGeometry = isMultiHeadSystem && !enforceNetWorkarea; + return useScreenGeometry ? m_geometry : m_availableGeometry; } QImage::Format QXcbScreen::format() const @@ -468,8 +606,9 @@ QDpi QXcbScreen::logicalDpi() const if (overrideDpi) return QDpi(overrideDpi, overrideDpi); - if (m_forcedDpi > 0) { - return QDpi(m_forcedDpi, m_forcedDpi); + const int forcedDpi = m_virtualDesktop->forcedDpi(); + if (forcedDpi > 0) { + return QDpi(forcedDpi, forcedDpi); } return virtualDpi(); } @@ -581,19 +720,14 @@ void QXcbScreen::updateGeometry(xcb_timestamp_t timestamp) if (!connection()->hasXRandr()) return; - xcb_randr_get_crtc_info_cookie_t crtcCookie = - xcb_randr_get_crtc_info_unchecked(xcb_connection(), m_crtc, timestamp); - xcb_randr_get_crtc_info_reply_t *crtc = - xcb_randr_get_crtc_info_reply(xcb_connection(), crtcCookie, NULL); - if (crtc) { + auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(), + m_crtc, timestamp); + if (crtc) updateGeometry(QRect(crtc->x, crtc->y, crtc->width, crtc->height), crtc->rotation); - free(crtc); - } } -void QXcbScreen::updateGeometry(const QRect &geom, uint8_t rotation) +void QXcbScreen::updateGeometry(const QRect &geometry, uint8_t rotation) { - QRect xGeometry = geom; switch (rotation) { case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal m_orientation = Qt::LandscapeOrientation; @@ -617,12 +751,12 @@ void QXcbScreen::updateGeometry(const QRect &geom, uint8_t rotation) // is known (probably back-calculated from DPI and resolution), // e.g. on VNC or with some hardware. if (m_sizeMillimeters.isEmpty()) - m_sizeMillimeters = sizeInMillimeters(xGeometry.size(), virtualDpi()); + m_sizeMillimeters = sizeInMillimeters(geometry.size(), virtualDpi()); - qreal dpi = xGeometry.width() / physicalSize().width() * qreal(25.4); + qreal dpi = geometry.width() / physicalSize().width() * qreal(25.4); m_pixelDensity = qMax(1, qRound(dpi/96)); - m_geometry = QRect(xGeometry.topLeft(), xGeometry.size()); - m_availableGeometry = xGeometry & m_virtualDesktop->workArea(); + m_geometry = geometry; + m_availableGeometry = geometry & m_virtualDesktop->workArea(); QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), m_geometry, m_availableGeometry); } @@ -645,13 +779,11 @@ void QXcbScreen::updateRefreshRate(xcb_randr_mode_t mode) // we can safely use get_screen_resources_current here, because in order to // get here, we must have called get_screen_resources before - xcb_randr_get_screen_resources_current_cookie_t resourcesCookie = - xcb_randr_get_screen_resources_current_unchecked(xcb_connection(), screen()->root); - xcb_randr_get_screen_resources_current_reply_t *resources = - xcb_randr_get_screen_resources_current_reply(xcb_connection(), resourcesCookie, NULL); + auto resources = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_screen_resources_current, + xcb_connection(), screen()->root); if (resources) { xcb_randr_mode_info_iterator_t modesIter = - xcb_randr_get_screen_resources_current_modes_iterator(resources); + xcb_randr_get_screen_resources_current_modes_iterator(resources.get()); for (; modesIter.rem; xcb_randr_mode_info_next(&modesIter)) { xcb_randr_mode_info_t *modeInfo = modesIter.data; if (modeInfo->id == mode) { @@ -662,29 +794,19 @@ void QXcbScreen::updateRefreshRate(xcb_randr_mode_t mode) } } - free(resources); QWindowSystemInterface::handleScreenRefreshRateChange(QPlatformScreen::screen(), m_refreshRate); } } -static xcb_get_geometry_reply_t *getGeometryUnchecked(xcb_connection_t *connection, xcb_window_t window) -{ - const xcb_get_geometry_cookie_t geometry_cookie = xcb_get_geometry_unchecked(connection, window); - return xcb_get_geometry_reply(connection, geometry_cookie, NULL); -} - static inline bool translate(xcb_connection_t *connection, xcb_window_t child, xcb_window_t parent, int *x, int *y) { - const xcb_translate_coordinates_cookie_t translate_cookie = - xcb_translate_coordinates_unchecked(connection, child, parent, *x, *y); - xcb_translate_coordinates_reply_t *translate_reply = - xcb_translate_coordinates_reply(connection, translate_cookie, NULL); + auto translate_reply = Q_XCB_REPLY_UNCHECKED(xcb_translate_coordinates, + connection, child, parent, *x, *y); if (!translate_reply) return false; *x = translate_reply->dst_x; *y = translate_reply->dst_y; - free(translate_reply); return true; } @@ -698,22 +820,20 @@ QPixmap QXcbScreen::grabWindow(WId window, int xIn, int yIn, int width, int heig QXcbScreen *screen = const_cast<QXcbScreen *>(this); xcb_window_t root = screen->root(); - xcb_get_geometry_reply_t *rootReply = getGeometryUnchecked(xcb_connection(), root); + auto rootReply = Q_XCB_REPLY_UNCHECKED(xcb_get_geometry, xcb_connection(), root); if (!rootReply) return QPixmap(); const quint8 rootDepth = rootReply->depth; - free(rootReply); QSize windowSize; quint8 effectiveDepth = 0; if (window) { - xcb_get_geometry_reply_t *windowReply = getGeometryUnchecked(xcb_connection(), window); + auto windowReply = Q_XCB_REPLY_UNCHECKED(xcb_get_geometry, xcb_connection(), window); if (!windowReply) return QPixmap(); windowSize = QSize(windowReply->width, windowReply->height); effectiveDepth = windowReply->depth; - free(windowReply); if (effectiveDepth == rootDepth) { // if the depth of the specified window and the root window are the // same, grab pixels from the root window (so that we get the any @@ -738,14 +858,12 @@ QPixmap QXcbScreen::grabWindow(WId window, int xIn, int yIn, int width, int heig if (height < 0) height = windowSize.height() - yIn; - xcb_get_window_attributes_reply_t *attributes_reply = - xcb_get_window_attributes_reply(xcb_connection(), xcb_get_window_attributes_unchecked(xcb_connection(), window), NULL); + auto attributes_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_window_attributes, xcb_connection(), window); if (!attributes_reply) return QPixmap(); const xcb_visualtype_t *visual = screen->visualForId(attributes_reply->visual); - free(attributes_reply); xcb_pixmap_t pixmap = xcb_generate_id(xcb_connection()); xcb_create_pixmap(xcb_connection(), effectiveDepth, pixmap, window, width, height); @@ -765,101 +883,45 @@ QPixmap QXcbScreen::grabWindow(WId window, int xIn, int yIn, int width, int heig return result; } -static bool parseXftInt(const QByteArray& stringValue, int *value) +QXcbXSettings *QXcbScreen::xSettings() const { - Q_ASSERT(value != 0); - bool ok; - *value = stringValue.toInt(&ok); - return ok; + return m_virtualDesktop->xSettings(); } -static QFontEngine::HintStyle parseXftHintStyle(const QByteArray& stringValue) +QByteArray QXcbScreen::getOutputProperty(xcb_atom_t atom) const { - if (stringValue == "hintfull") - return QFontEngine::HintFull; - else if (stringValue == "hintnone") - return QFontEngine::HintNone; - else if (stringValue == "hintmedium") - return QFontEngine::HintMedium; - else if (stringValue == "hintslight") - return QFontEngine::HintLight; - - return QFontEngine::HintStyle(-1); -} + QByteArray result; -static QFontEngine::SubpixelAntialiasingType parseXftRgba(const QByteArray& stringValue) -{ - if (stringValue == "none") - return QFontEngine::Subpixel_None; - else if (stringValue == "rgb") - return QFontEngine::Subpixel_RGB; - else if (stringValue == "bgr") - return QFontEngine::Subpixel_BGR; - else if (stringValue == "vrgb") - return QFontEngine::Subpixel_VRGB; - else if (stringValue == "vbgr") - return QFontEngine::Subpixel_VBGR; - - return QFontEngine::SubpixelAntialiasingType(-1); -} - -bool QXcbScreen::xResource(const QByteArray &identifier, - const QByteArray &expectedIdentifier, - QByteArray& stringValue) -{ - if (identifier.startsWith(expectedIdentifier)) { - stringValue = identifier.mid(expectedIdentifier.size()); - return true; + auto reply = Q_XCB_REPLY(xcb_randr_get_output_property, xcb_connection(), + m_output, atom, XCB_ATOM_ANY, 0, 100, false, false); + if (reply && reply->type == XCB_ATOM_INTEGER && reply->format == 8) { + quint8 *data = new quint8[reply->num_items]; + memcpy(data, xcb_randr_get_output_property_data(reply.get()), reply->num_items); + result = QByteArray(reinterpret_cast<const char *>(data), reply->num_items); + delete[] data; } - return false; + + return result; } -void QXcbScreen::readXResources() +QByteArray QXcbScreen::getEdid() const { - int offset = 0; - QByteArray resources; - while(1) { - xcb_get_property_reply_t *reply = - xcb_get_property_reply(xcb_connection(), - xcb_get_property_unchecked(xcb_connection(), false, screen()->root, - XCB_ATOM_RESOURCE_MANAGER, - XCB_ATOM_STRING, offset/4, 8192), NULL); - bool more = false; - if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) { - resources += QByteArray((const char *)xcb_get_property_value(reply), xcb_get_property_value_length(reply)); - offset += xcb_get_property_value_length(reply); - more = reply->bytes_after != 0; - } - - if (reply) - free(reply); - - if (!more) - break; + QByteArray result; + if (!connection()->hasXRandr()) + return result; + + // Try a bunch of atoms + xcb_atom_t atom = connection()->internAtom("EDID"); + result = getOutputProperty(atom); + if (result.isEmpty()) { + atom = connection()->internAtom("EDID_DATA"); + result = getOutputProperty(atom); } - - QList<QByteArray> split = resources.split('\n'); - for (int i = 0; i < split.size(); ++i) { - const QByteArray &r = split.at(i); - int value; - QByteArray stringValue; - if (xResource(r, "Xft.dpi:\t", stringValue)) { - if (parseXftInt(stringValue, &value)) - m_forcedDpi = value; - } else if (xResource(r, "Xft.hintstyle:\t", stringValue)) { - m_hintStyle = parseXftHintStyle(stringValue); - } else if (xResource(r, "Xft.antialias:\t", stringValue)) { - if (parseXftInt(stringValue, &value)) - m_antialiasingEnabled = value; - } else if (xResource(r, "Xft.rgba:\t", stringValue)) { - m_subpixelType = parseXftRgba(stringValue); - } + if (result.isEmpty()) { + atom = connection()->internAtom("XFree86_DDC_EDID1_RAWDATA"); + result = getOutputProperty(atom); } -} - -QXcbXSettings *QXcbScreen::xSettings() const -{ - return m_virtualDesktop->xSettings(); + return result; } static inline void formatRect(QDebug &debug, const QRect r) diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h index 4163be2969..842738b622 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.h +++ b/src/plugins/platforms/xcb/qxcbscreen.h @@ -53,6 +53,8 @@ #include <private/qfontengine_p.h> +#include <QtEdidSupport/private/qedidparser_p.h> + QT_BEGIN_NAMESPACE class QXcbConnection; @@ -91,9 +93,28 @@ public: void handleXFixesSelectionNotify(xcb_xfixes_selection_notify_event_t *notify_event); void subscribeToXFixesSelectionNotify(); + int forcedDpi() const { return m_forcedDpi; } + QFontEngine::HintStyle hintStyle() const { return m_hintStyle; } + QFontEngine::SubpixelAntialiasingType subpixelType() const { return m_subpixelType; } + int antialiasingEnabled() const { return m_antialiasingEnabled; } + + QString windowManagerName() const { return m_windowManagerName; } + bool syncRequestSupported() const { return m_syncRequestSupported; } + + QSurfaceFormat surfaceFormatFor(const QSurfaceFormat &format) const; + + const xcb_visualtype_t *visualForFormat(const QSurfaceFormat &format) const; + const xcb_visualtype_t *visualForId(xcb_visualid_t) const; + quint8 depthOfVisual(xcb_visualid_t) const; + private: QRect getWorkArea() const; + static bool xResource(const QByteArray &identifier, + const QByteArray &expectedIdentifier, + QByteArray &stringValue); + void readXResources(); + xcb_screen_t *m_screen; const int m_number; QList<QPlatformScreen *> m_screens; @@ -103,6 +124,15 @@ private: bool m_compositingActive = false; QRect m_workArea; + + int m_forcedDpi = -1; + QFontEngine::HintStyle m_hintStyle = QFontEngine::HintStyle(-1); + QFontEngine::SubpixelAntialiasingType m_subpixelType = QFontEngine::SubpixelAntialiasingType(-1); + int m_antialiasingEnabled = -1; + QString m_windowManagerName; + bool m_syncRequestSupported = false; + QMap<xcb_visualid_t, xcb_visualtype_t> m_visuals; + QMap<xcb_visualid_t, quint8> m_visualDepths; }; class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen @@ -119,8 +149,12 @@ public: QWindow *topLevelAt(const QPoint &point) const override; + QString manufacturer() const override; + QString model() const override; + QString serialNumber() const override; + QRect geometry() const override { return m_geometry; } - QRect availableGeometry() const override {return m_availableGeometry;} + QRect availableGeometry() const override; int depth() const override { return screen()->root_depth; } QImage::Format format() const override; QSizeF physicalSize() const override { return m_sizeMillimeters; } @@ -152,37 +186,35 @@ public: void setCrtc(xcb_randr_crtc_t crtc) { m_crtc = crtc; } void windowShown(QXcbWindow *window); - QString windowManagerName() const { return m_windowManagerName; } - bool syncRequestSupported() const { return m_syncRequestSupported; } + QString windowManagerName() const { return m_virtualDesktop->windowManagerName(); } + bool syncRequestSupported() const { return m_virtualDesktop->syncRequestSupported(); } QSurfaceFormat surfaceFormatFor(const QSurfaceFormat &format) const; - const xcb_visualtype_t *visualForFormat(const QSurfaceFormat &format) const; - const xcb_visualtype_t *visualForId(xcb_visualid_t) const; - quint8 depthOfVisual(xcb_visualid_t) const; + const xcb_visualtype_t *visualForFormat(const QSurfaceFormat &format) const { return m_virtualDesktop->visualForFormat(format); } + const xcb_visualtype_t *visualForId(xcb_visualid_t visualid) const; + quint8 depthOfVisual(xcb_visualid_t visualid) const { return m_virtualDesktop->depthOfVisual(visualid); } QString name() const override { return m_outputName; } void handleScreenChange(xcb_randr_screen_change_notify_event_t *change_event); - void updateGeometry(const QRect &geom, uint8_t rotation); + void updateGeometry(const QRect &geometry, uint8_t rotation); void updateGeometry(xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateAvailableGeometry(); void updateRefreshRate(xcb_randr_mode_t mode); - void readXResources(); - - QFontEngine::HintStyle hintStyle() const { return m_hintStyle; } - QFontEngine::SubpixelAntialiasingType subpixelType() const { return m_subpixelType; } - int antialiasingEnabled() const { return m_antialiasingEnabled; } + QFontEngine::HintStyle hintStyle() const { return m_virtualDesktop->hintStyle(); } + QFontEngine::SubpixelAntialiasingType subpixelType() const { return m_virtualDesktop->subpixelType(); } + int antialiasingEnabled() const { return m_virtualDesktop->antialiasingEnabled(); } QXcbXSettings *xSettings() const; private: - static bool xResource(const QByteArray &identifier, - const QByteArray &expectedIdentifier, - QByteArray &stringValue); void sendStartupMessage(const QByteArray &message) const; + QByteArray getOutputProperty(xcb_atom_t atom) const; + QByteArray getEdid() const; + QXcbVirtualDesktop *m_virtualDesktop; xcb_randr_output_t m_output; xcb_randr_crtc_t m_crtc; @@ -198,17 +230,10 @@ private: QSize m_virtualSize; QSizeF m_virtualSizeMillimeters; Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; - QString m_windowManagerName; - bool m_syncRequestSupported = false; - QMap<xcb_visualid_t, xcb_visualtype_t> m_visuals; - QMap<xcb_visualid_t, quint8> m_visualDepths; QXcbCursor *m_cursor; int m_refreshRate = 60; - int m_forcedDpi = -1; int m_pixelDensity = 1; - QFontEngine::HintStyle m_hintStyle = QFontEngine::HintStyle(-1); - QFontEngine::SubpixelAntialiasingType m_subpixelType = QFontEngine::SubpixelAntialiasingType(-1); - int m_antialiasingEnabled = -1; + QEdidParser m_edid; }; #ifndef QT_NO_DEBUG_STREAM diff --git a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp index fb0a4a3939..c98879c7df 100644 --- a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp +++ b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp @@ -85,13 +85,10 @@ QXcbSystemTrayTracker::QXcbSystemTrayTracker(QXcbConnection *connection, xcb_window_t QXcbSystemTrayTracker::locateTrayWindow(const QXcbConnection *connection, xcb_atom_t selection) { - xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(connection->xcb_connection(), selection); - xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(connection->xcb_connection(), cookie, 0); + auto reply = Q_XCB_REPLY(xcb_get_selection_owner, connection->xcb_connection(), selection); if (!reply) return 0; - const xcb_window_t result = reply->owner; - free(reply); - return result; + return reply->owner; } // API for QPlatformNativeInterface/QPlatformSystemTrayIcon: Request a window @@ -119,7 +116,7 @@ xcb_window_t QXcbSystemTrayTracker::trayWindow() m_connection->addWindowEventListener(m_trayWindow, this); const quint32 mask = XCB_CW_EVENT_MASK; const quint32 value = XCB_EVENT_MASK_STRUCTURE_NOTIFY; - Q_XCB_CALL2(xcb_change_window_attributes(m_connection->xcb_connection(), m_trayWindow, mask, &value), m_connection); + xcb_change_window_attributes(m_connection->xcb_connection(), m_trayWindow, mask, &value); } } return m_trayWindow; @@ -130,23 +127,16 @@ xcb_window_t QXcbSystemTrayTracker::trayWindow() // does not work for the QWindow parented on the tray. QRect QXcbSystemTrayTracker::systemTrayWindowGlobalGeometry(xcb_window_t window) const { - xcb_connection_t *conn = m_connection->xcb_connection(); - xcb_get_geometry_reply_t *geomReply = - xcb_get_geometry_reply(conn, xcb_get_geometry(conn, window), 0); + auto geomReply = Q_XCB_REPLY(xcb_get_geometry, conn, window); if (!geomReply) return QRect(); - xcb_translate_coordinates_reply_t *translateReply = - xcb_translate_coordinates_reply(conn, xcb_translate_coordinates(conn, window, m_connection->rootWindow(), 0, 0), 0); - if (!translateReply) { - free(geomReply); + auto translateReply = Q_XCB_REPLY(xcb_translate_coordinates, conn, window, m_connection->rootWindow(), 0, 0); + if (!translateReply) return QRect(); - } - const QRect result(QPoint(translateReply->dst_x, translateReply->dst_y), QSize(geomReply->width, geomReply->height)); - free(translateReply); - return result; + return QRect(QPoint(translateReply->dst_x, translateReply->dst_y), QSize(geomReply->width, geomReply->height)); } inline void QXcbSystemTrayTracker::emitSystemTrayWindowChanged() @@ -180,24 +170,18 @@ bool QXcbSystemTrayTracker::visualHasAlphaChannel() xcb_atom_t tray_atom = m_connection->atom(QXcbAtom::_NET_SYSTEM_TRAY_VISUAL); // Get the xcb property for the _NET_SYSTEM_TRAY_VISUAL atom - xcb_get_property_cookie_t systray_atom_cookie; - xcb_get_property_reply_t *systray_atom_reply; - - systray_atom_cookie = xcb_get_property_unchecked(m_connection->xcb_connection(), false, m_trayWindow, + auto systray_atom_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, m_connection->xcb_connection(), + false, m_trayWindow, tray_atom, XCB_ATOM_VISUALID, 0, 1); - systray_atom_reply = xcb_get_property_reply(m_connection->xcb_connection(), systray_atom_cookie, 0); - if (!systray_atom_reply) return false; xcb_visualid_t systrayVisualId = XCB_NONE; - if (systray_atom_reply->value_len > 0 && xcb_get_property_value_length(systray_atom_reply) > 0) { - xcb_visualid_t * vids = (uint32_t *)xcb_get_property_value(systray_atom_reply); + if (systray_atom_reply->value_len > 0 && xcb_get_property_value_length(systray_atom_reply.get()) > 0) { + xcb_visualid_t * vids = (uint32_t *)xcb_get_property_value(systray_atom_reply.get()); systrayVisualId = vids[0]; } - free(systray_atom_reply); - if (systrayVisualId != XCB_NONE) { quint8 depth = m_connection->primaryScreen()->depthOfVisual(systrayVisualId); return depth == 32; diff --git a/src/plugins/platforms/xcb/qxcbvulkaninstance.cpp b/src/plugins/platforms/xcb/qxcbvulkaninstance.cpp new file mode 100644 index 0000000000..4d540defa9 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbvulkaninstance.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#include "qxcbvulkaninstance.h" +#include "qxcbwindow.h" +#include "qxcbscreen.h" + +QT_BEGIN_NAMESPACE + +QXcbVulkanInstance::QXcbVulkanInstance(QVulkanInstance *instance) + : m_instance(instance), + m_getPhysDevPresSupport(nullptr), + m_createSurface(nullptr), + m_destroySurface(nullptr) +{ + if (qEnvironmentVariableIsSet("QT_VULKAN_LIB")) + m_lib.setFileName(QString::fromUtf8(qgetenv("QT_VULKAN_LIB"))); + else + m_lib.setFileName(QStringLiteral("vulkan")); + + if (!m_lib.load()) { + qWarning("Failed to load %s: %s", qPrintable(m_lib.fileName()), qPrintable(m_lib.errorString())); + return; + } + + init(&m_lib); +} + +QXcbVulkanInstance::~QXcbVulkanInstance() +{ +} + +void QXcbVulkanInstance::createOrAdoptInstance() +{ + initInstance(m_instance, QByteArrayList() << QByteArrayLiteral("VK_KHR_xcb_surface")); + + if (!m_vkInst) + return; + + m_getPhysDevPresSupport = reinterpret_cast<PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR>( + m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceXcbPresentationSupportKHR")); + if (!m_getPhysDevPresSupport) + qWarning("Failed to find vkGetPhysicalDeviceXcbPresentationSupportKHR"); +} + +bool QXcbVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + QWindow *window) +{ + if (!m_getPhysDevPresSupport || !m_getPhysDevSurfaceSupport) + return true; + + QXcbWindow *w = static_cast<QXcbWindow *>(window->handle()); + if (!w) { + qWarning("Attempted to call supportsPresent() without a valid platform window"); + return false; + } + xcb_connection_t *connection = w->xcbScreen()->xcb_connection(); + bool ok = m_getPhysDevPresSupport(physicalDevice, queueFamilyIndex, connection, w->visualId()); + + VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window); + VkBool32 supported = false; + m_getPhysDevSurfaceSupport(physicalDevice, queueFamilyIndex, surface, &supported); + ok &= bool(supported); + + return ok; +} + +VkSurfaceKHR QXcbVulkanInstance::createSurface(QXcbWindow *window) +{ + VkSurfaceKHR surface = 0; + + if (!m_createSurface) { + m_createSurface = reinterpret_cast<PFN_vkCreateXcbSurfaceKHR>( + m_vkGetInstanceProcAddr(m_vkInst, "vkCreateXcbSurfaceKHR")); + } + if (!m_createSurface) { + qWarning("Failed to find vkCreateXcbSurfaceKHR"); + return surface; + } + if (!m_destroySurface) { + m_destroySurface = reinterpret_cast<PFN_vkDestroySurfaceKHR>( + m_vkGetInstanceProcAddr(m_vkInst, "vkDestroySurfaceKHR")); + } + if (!m_destroySurface) { + qWarning("Failed to find vkDestroySurfaceKHR"); + return surface; + } + + VkXcbSurfaceCreateInfoKHR surfaceInfo; + memset(&surfaceInfo, 0, sizeof(surfaceInfo)); + surfaceInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + surfaceInfo.connection = window->xcbScreen()->xcb_connection(); + surfaceInfo.window = window->xcb_window(); + VkResult err = m_createSurface(m_vkInst, &surfaceInfo, nullptr, &surface); + if (err != VK_SUCCESS) + qWarning("Failed to create Vulkan surface: %d", err); + + return surface; +} + +void QXcbVulkanInstance::destroySurface(VkSurfaceKHR surface) +{ + if (m_destroySurface && surface) + m_destroySurface(m_vkInst, surface, nullptr); +} + +void QXcbVulkanInstance::presentQueued(QWindow *window) +{ + QXcbWindow *w = static_cast<QXcbWindow *>(window->handle()); + if (!w) { + qWarning("Attempted to call presentQueued() without a valid platform window"); + return; + } + + if (w->needsSync()) + w->postSyncWindowRequest(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbvulkaninstance.h b/src/plugins/platforms/xcb/qxcbvulkaninstance.h new file mode 100644 index 0000000000..dbe057d944 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbvulkaninstance.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#ifndef QXCBVULKANINSTANCE_H +#define QXCBVULKANINSTANCE_H + +#if defined(VULKAN_H_) && !defined(VK_USE_PLATFORM_XCB_KHR) +#error "vulkan.h included without xcb WSI" +#endif + +#define VK_USE_PLATFORM_XCB_KHR + +#include <QtVulkanSupport/private/qbasicvulkanplatforminstance_p.h> +#include <QLibrary> + +QT_BEGIN_NAMESPACE + +class QXcbWindow; + +class QXcbVulkanInstance : public QBasicPlatformVulkanInstance +{ +public: + QXcbVulkanInstance(QVulkanInstance *instance); + ~QXcbVulkanInstance(); + + void createOrAdoptInstance() override; + bool supportsPresent(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, QWindow *window) override; + void presentQueued(QWindow *window) override; + + VkSurfaceKHR createSurface(QXcbWindow *window); + void destroySurface(VkSurfaceKHR surface); + +private: + QVulkanInstance *m_instance; + QLibrary m_lib; + PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR m_getPhysDevPresSupport; + PFN_vkCreateXcbSurfaceKHR m_createSurface; + PFN_vkDestroySurfaceKHR m_destroySurface; +}; + +QT_END_NAMESPACE + +#endif // QXCBVULKANINSTANCE_H diff --git a/src/plugins/platforms/xcb/qxcbvulkanwindow.cpp b/src/plugins/platforms/xcb/qxcbvulkanwindow.cpp new file mode 100644 index 0000000000..25bc340f97 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbvulkanwindow.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#include "qxcbvulkanwindow.h" + +QT_BEGIN_NAMESPACE + +QXcbVulkanWindow::QXcbVulkanWindow(QWindow *window) + : QXcbWindow(window), + m_surface(0) +{ +} + +QXcbVulkanWindow::~QXcbVulkanWindow() +{ + if (m_surface) { + QVulkanInstance *inst = window()->vulkanInstance(); + if (inst) + static_cast<QXcbVulkanInstance *>(inst->handle())->destroySurface(m_surface); + } +} + +void QXcbVulkanWindow::resolveFormat(const QSurfaceFormat &format) +{ + m_format = format; + if (m_format.redBufferSize() <= 0) + m_format.setRedBufferSize(8); + if (m_format.greenBufferSize() <= 0) + m_format.setGreenBufferSize(8); + if (m_format.blueBufferSize() <= 0) + m_format.setBlueBufferSize(8); +} + +// No createVisual() needed, use the default that picks one based on the R/G/B/A size. + +VkSurfaceKHR *QXcbVulkanWindow::surface() +{ + if (m_surface) + return &m_surface; + + QVulkanInstance *inst = window()->vulkanInstance(); + if (!inst) { + qWarning("Attempted to create Vulkan surface without an instance; was QWindow::setVulkanInstance() called?"); + return nullptr; + } + QXcbVulkanInstance *xcbinst = static_cast<QXcbVulkanInstance *>(inst->handle()); + m_surface = xcbinst->createSurface(this); + + return &m_surface; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbvulkanwindow.h b/src/plugins/platforms/xcb/qxcbvulkanwindow.h new file mode 100644 index 0000000000..43c96820c5 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbvulkanwindow.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QXCBVULKANWINDOW_H +#define QXCBVULKANWINDOW_H + +#include "qxcbwindow.h" +#include "qxcbvulkaninstance.h" + +QT_BEGIN_NAMESPACE + +class QXcbVulkanWindow : public QXcbWindow +{ +public: + QXcbVulkanWindow(QWindow *window); + ~QXcbVulkanWindow(); + + VkSurfaceKHR *surface(); + +protected: + void resolveFormat(const QSurfaceFormat &format) override; + +private: + VkSurfaceKHR m_surface; +}; + +QT_END_NAMESPACE + +#endif // QXCBVULKANWINDOW_H diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index c4649ac818..c8a668b72c 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -227,6 +227,12 @@ static inline QImage::Format imageFormatForVisual(int depth, quint32 red_mask, q return QImage::Format_RGB555; } break; +#if QT_CONFIG(xcb_native_painting) + case 8: + if (QXcbIntegration::instance() && QXcbIntegration::instance()->nativePaintingEnabled()) + return QImage::Format_Indexed8; + break; +#endif default: break; } @@ -420,6 +426,19 @@ void QXcbWindow::create() qWarning() << "Failed to use requested visual id."; } + if (parent()) { + // When using a Vulkan QWindow via QWidget::createWindowContainer() we + // must make sure the visuals are compatible. Now, the parent will be + // of RasterGLSurface which typically chooses a GLX/EGL compatible + // visual which may not be what the Vulkan window would choose. + // Therefore, take the parent's visual. + if (window()->surfaceType() == QSurface::VulkanSurface + && parent()->window()->surfaceType() != QSurface::VulkanSurface) + { + visual = platformScreen->visualForId(static_cast<QXcbWindow *>(parent())->visualId()); + } + } + if (!visual) visual = createVisual(); @@ -445,11 +464,11 @@ void QXcbWindow::create() if ((window()->supportsOpenGL() && haveOpenGL) || m_format.hasAlpha()) { m_cmap = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_colormap(xcb_connection(), - XCB_COLORMAP_ALLOC_NONE, - m_cmap, - xcb_parent_id, - m_visualId)); + xcb_create_colormap(xcb_connection(), + XCB_COLORMAP_ALLOC_NONE, + m_cmap, + xcb_parent_id, + m_visualId); mask |= XCB_CW_COLORMAP; } @@ -465,23 +484,23 @@ void QXcbWindow::create() }; m_window = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_window(xcb_connection(), - m_depth, - m_window, // window id - xcb_parent_id, // parent window id - rect.x(), - rect.y(), - rect.width(), - rect.height(), - 0, // border width - XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class - m_visualId, // visual - mask, - values)); + xcb_create_window(xcb_connection(), + m_depth, + m_window, // window id + xcb_parent_id, // parent window id + rect.x(), + rect.y(), + rect.width(), + rect.height(), + 0, // border width + XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class + m_visualId, // visual + mask, + values); connection()->addWindowEventListener(m_window, this); - Q_XCB_CALL(xcb_change_window_attributes(xcb_connection(), m_window, mask, values)); + xcb_change_window_attributes(xcb_connection(), m_window, mask, values); propagateSizeHints(); @@ -499,43 +518,43 @@ void QXcbWindow::create() if (window()->flags() & Qt::WindowContextHelpButtonHint) properties[propertyCount++] = atom(QXcbAtom::_NET_WM_CONTEXT_HELP); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_window, - atom(QXcbAtom::WM_PROTOCOLS), - XCB_ATOM_ATOM, - 32, - propertyCount, - properties)); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_window, + atom(QXcbAtom::WM_PROTOCOLS), + XCB_ATOM_ATOM, + 32, + propertyCount, + properties); m_syncValue.hi = 0; m_syncValue.lo = 0; const QByteArray wmClass = QXcbIntegration::instance()->wmClass(); if (!wmClass.isEmpty()) { - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, - m_window, atom(QXcbAtom::WM_CLASS), - XCB_ATOM_STRING, 8, wmClass.size(), wmClass.constData())); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, + m_window, atom(QXcbAtom::WM_CLASS), + XCB_ATOM_STRING, 8, wmClass.size(), wmClass.constData()); } if (m_usingSyncProtocol) { m_syncCounter = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_sync_create_counter(xcb_connection(), m_syncCounter, m_syncValue)); + xcb_sync_create_counter(xcb_connection(), m_syncCounter, m_syncValue); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_window, - atom(QXcbAtom::_NET_WM_SYNC_REQUEST_COUNTER), - XCB_ATOM_CARDINAL, - 32, - 1, - &m_syncCounter)); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_window, + atom(QXcbAtom::_NET_WM_SYNC_REQUEST_COUNTER), + XCB_ATOM_CARDINAL, + 32, + 1, + &m_syncCounter); } // set the PID to let the WM kill the application if unresponsive quint32 pid = getpid(); - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - atom(QXcbAtom::_NET_WM_PID), XCB_ATOM_CARDINAL, 32, - 1, &pid)); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + atom(QXcbAtom::_NET_WM_PID), XCB_ATOM_CARDINAL, 32, + 1, &pid); xcb_wm_hints_t hints; memset(&hints, 0, sizeof(hints)); @@ -546,23 +565,27 @@ void QXcbWindow::create() xcb_set_wm_hints(xcb_connection(), m_window, &hints); xcb_window_t leader = connection()->clientLeader(); - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - atom(QXcbAtom::WM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32, - 1, &leader)); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + atom(QXcbAtom::WM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32, + 1, &leader); /* Add XEMBED info; this operation doesn't initiate the embedding. */ quint32 data[] = { XEMBED_VERSION, XEMBED_MAPPED }; - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - atom(QXcbAtom::_XEMBED_INFO), - atom(QXcbAtom::_XEMBED_INFO), - 32, 2, (void *)data)); - + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + atom(QXcbAtom::_XEMBED_INFO), + atom(QXcbAtom::_XEMBED_INFO), + 32, 2, (void *)data); #if QT_CONFIG(xinput2) - connection()->xi2Select(m_window); + if (connection()->hasXInput2()) { + if (connection()->xi2MouseEventsDisabled()) + connection()->xi2SelectDeviceEventsCompatibility(m_window); + else + connection()->xi2SelectDeviceEvents(m_window); + } #endif - setWindowState(window()->windowState()); + setWindowState(window()->windowStates()); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); @@ -571,7 +594,7 @@ void QXcbWindow::create() #if QT_CONFIG(xcb_xlib) // force sync to read outstanding requests - see QTBUG-29106 - XSync(DISPLAY_FROM_XCB(platformScreen), false); + XSync(static_cast<Display*>(platformScreen->connection()->xlib_display()), false); #endif #ifndef QT_NO_DRAGANDDROP @@ -581,6 +604,9 @@ void QXcbWindow::create() const qreal opacity = qt_window_private(window())->opacity; if (!qFuzzyCompare(opacity, qreal(1.0))) setOpacity(opacity); + + setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window())); + if (window()->isTopLevel()) setWindowIcon(window()->icon()); @@ -618,7 +644,7 @@ void QXcbWindow::destroy() connection()->setMouseGrabber(Q_NULLPTR); if (m_syncCounter && m_usingSyncProtocol) - Q_XCB_CALL(xcb_sync_destroy_counter(xcb_connection(), m_syncCounter)); + xcb_sync_destroy_counter(xcb_connection(), m_syncCounter); if (m_window) { if (m_netWmUserTimeWindow) { xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW)); @@ -629,7 +655,7 @@ void QXcbWindow::destroy() m_netWmUserTimeWindow = XCB_NONE; } connection()->removeWindowEventListener(m_window); - Q_XCB_CALL(xcb_destroy_window(xcb_connection(), m_window)); + xcb_destroy_window(xcb_connection(), m_window); m_window = 0; } if (m_cmap) { @@ -664,7 +690,7 @@ void QXcbWindow::setGeometry(const QRect &rect) qBound<qint32>(1, wmGeometry.width(), XCOORD_MAX), qBound<qint32>(1, wmGeometry.height(), XCOORD_MAX), }; - Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values))); + xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values)); } else { const quint32 mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; const qint32 values[] = { @@ -673,7 +699,7 @@ void QXcbWindow::setGeometry(const QRect &rect) qBound<qint32>(1, wmGeometry.width(), XCOORD_MAX), qBound<qint32>(1, wmGeometry.height(), XCOORD_MAX), }; - Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values))); + xcb_configure_window(xcb_connection(), m_window, mask, reinterpret_cast<const quint32*>(values)); } xcb_flush(xcb_connection()); @@ -683,12 +709,10 @@ QMargins QXcbWindow::frameMargins() const { if (m_dirtyFrameMargins) { if (connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_FRAME_EXTENTS))) { - xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, m_window, - atom(QXcbAtom::_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4); - QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> reply( - xcb_get_property_reply(xcb_connection(), cookie, NULL)); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, m_window, + atom(QXcbAtom::_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4); if (reply && reply->type == XCB_ATOM_CARDINAL && reply->format == 32 && reply->value_len == 4) { - quint32 *data = (quint32 *)xcb_get_property_value(reply.data()); + quint32 *data = (quint32 *)xcb_get_property_value(reply.get()); // _NET_FRAME_EXTENTS format is left, right, top, bottom m_frameMargins = QMargins(data[0], data[2], data[1], data[3]); m_dirtyFrameMargins = false; @@ -707,9 +731,7 @@ QMargins QXcbWindow::frameMargins() const connection()->wmSupport()->virtualRoots(); while (!foundRoot) { - xcb_query_tree_cookie_t cookie = xcb_query_tree_unchecked(xcb_connection(), parent); - - xcb_query_tree_reply_t *reply = xcb_query_tree_reply(xcb_connection(), cookie, NULL); + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, xcb_connection(), parent); if (reply) { if (reply->root == reply->parent || virtualRoots.indexOf(reply->parent) != -1 || reply->parent == XCB_WINDOW_NONE) { foundRoot = true; @@ -717,8 +739,6 @@ QMargins QXcbWindow::frameMargins() const window = parent; parent = reply->parent; } - - free(reply); } else { m_dirtyFrameMargins = false; m_frameMargins = QMargins(); @@ -728,23 +748,12 @@ QMargins QXcbWindow::frameMargins() const QPoint offset; - xcb_translate_coordinates_reply_t *reply = - xcb_translate_coordinates_reply( - xcb_connection(), - xcb_translate_coordinates(xcb_connection(), window, parent, 0, 0), - NULL); - + auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), window, parent, 0, 0); if (reply) { offset = QPoint(reply->dst_x, reply->dst_y); - free(reply); } - xcb_get_geometry_reply_t *geom = - xcb_get_geometry_reply( - xcb_connection(), - xcb_get_geometry(xcb_connection(), parent), - NULL); - + auto geom = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), parent); if (geom) { // -- // add the border_width for the window managers frame... some window managers @@ -761,8 +770,6 @@ QMargins QXcbWindow::frameMargins() const int bottom = geom->height + geom->border_width - geometry().height() - offset.y(); m_frameMargins = QMargins(left, top, right, bottom); - - free(geom); } m_dirtyFrameMargins = false; @@ -789,12 +796,13 @@ static inline bool testShowWithoutActivating(const QWindow *window) 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()->windowState() & Qt::WindowMinimized) + if (window()->windowStates() & Qt::WindowMinimized) xcb_wm_hints_set_iconic(&hints); else xcb_wm_hints_set_normal(&hints); @@ -820,13 +828,13 @@ void QXcbWindow::show() if (!transientXcbParent) transientXcbParent = connection()->clientLeader(); if (transientXcbParent) { // ICCCM 4.1.2.6 - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, - 1, &transientXcbParent)); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, + 1, &transientXcbParent); } } if (!transientXcbParent) - Q_XCB_CALL(xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR)); + xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR); // update _MOTIF_WM_HINTS updateMotifWmHintsBeforeMap(); @@ -843,7 +851,7 @@ void QXcbWindow::show() if (window()->objectName() == QLatin1String("QSystemTrayIconSysWindow")) return; // defer showing until XEMBED_EMBEDDED_NOTIFY - Q_XCB_CALL(xcb_map_window(xcb_connection(), m_window)); + xcb_map_window(xcb_connection(), m_window); if (QGuiApplication::modalWindow() == window()) requestActivateWindow(); @@ -855,7 +863,7 @@ void QXcbWindow::show() void QXcbWindow::hide() { - Q_XCB_CALL(xcb_unmap_window(xcb_connection(), m_window)); + xcb_unmap_window(xcb_connection(), m_window); // send synthetic UnmapNotify event according to icccm 4.1.4 Q_DECLARE_XCB_EVENT(event, xcb_unmap_notify_event_t); @@ -863,8 +871,8 @@ void QXcbWindow::hide() event.event = xcbScreen()->root(); event.window = m_window; event.from_configure = false; - Q_XCB_CALL(xcb_send_event(xcb_connection(), false, xcbScreen()->root(), - XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&event)); + xcb_send_event(xcb_connection(), false, xcbScreen()->root(), + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&event); xcb_flush(xcb_connection()); @@ -1014,15 +1022,11 @@ static QtMotifWmHints getMotifWmHints(QXcbConnection *c, xcb_window_t window) { QtMotifWmHints hints; - xcb_get_property_cookie_t get_cookie = - xcb_get_property_unchecked(c->xcb_connection(), 0, window, c->atom(QXcbAtom::_MOTIF_WM_HINTS), - c->atom(QXcbAtom::_MOTIF_WM_HINTS), 0, 20); - - xcb_get_property_reply_t *reply = - xcb_get_property_reply(c->xcb_connection(), get_cookie, NULL); + 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)); + hints = *((QtMotifWmHints *)xcb_get_property_value(reply.get())); } else { hints.flags = 0L; hints.functions = MWM_FUNC_ALL; @@ -1031,24 +1035,22 @@ static QtMotifWmHints getMotifWmHints(QXcbConnection *c, xcb_window_t window) hints.status = 0L; } - free(reply); - return hints; } static void setMotifWmHints(QXcbConnection *c, xcb_window_t window, const QtMotifWmHints &hints) { if (hints.flags != 0l) { - Q_XCB_CALL2(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), c); + 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 { - Q_XCB_CALL2(xcb_delete_property(c->xcb_connection(), window, c->atom(QXcbAtom::_MOTIF_WM_HINTS)), c); + xcb_delete_property(c->xcb_connection(), window, c->atom(QXcbAtom::_MOTIF_WM_HINTS)); } } @@ -1056,15 +1058,12 @@ QXcbWindow::NetWmStates QXcbWindow::netWmStates() { NetWmStates result(0); - xcb_get_property_cookie_t get_cookie = - xcb_get_property_unchecked(xcb_connection(), 0, m_window, atom(QXcbAtom::_NET_WM_STATE), - XCB_ATOM_ATOM, 0, 1024); - - xcb_get_property_reply_t *reply = - xcb_get_property_reply(xcb_connection(), get_cookie, NULL); + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), + 0, m_window, atom(QXcbAtom::_NET_WM_STATE), + XCB_ATOM_ATOM, 0, 1024); if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) { - const xcb_atom_t *states = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply)); + const xcb_atom_t *states = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get())); const xcb_atom_t *statesEnd = states + reply->length; if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_ABOVE))) result |= NetWmStateAbove; @@ -1082,7 +1081,6 @@ QXcbWindow::NetWmStates QXcbWindow::netWmStates() result |= NetWmStateStaysOnTop; if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION))) result |= NetWmStateDemandsAttention; - free(reply); } else { #ifdef NET_WM_STATE_DEBUG printf("getting net wm state (%x), empty\n", m_window); @@ -1096,21 +1094,15 @@ void QXcbWindow::setNetWmStates(NetWmStates states) { QVector<xcb_atom_t> atoms; - xcb_get_property_cookie_t get_cookie = - xcb_get_property_unchecked(xcb_connection(), 0, m_window, atom(QXcbAtom::_NET_WM_STATE), - XCB_ATOM_ATOM, 0, 1024); - - xcb_get_property_reply_t *reply = - xcb_get_property_reply(xcb_connection(), get_cookie, NULL); - + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), + 0, m_window, atom(QXcbAtom::_NET_WM_STATE), + XCB_ATOM_ATOM, 0, 1024); if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM && reply->value_len > 0) { - const xcb_atom_t *data = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply)); + const xcb_atom_t *data = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get())); atoms.resize(reply->value_len); memcpy((void *)&atoms.first(), (void *)data, reply->value_len * sizeof(xcb_atom_t)); } - free(reply); - if (states & NetWmStateAbove && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_ABOVE))) atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_ABOVE)); if (states & NetWmStateBelow && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_BELOW))) @@ -1129,11 +1121,11 @@ void QXcbWindow::setNetWmStates(NetWmStates states) atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION)); if (atoms.isEmpty()) { - Q_XCB_CALL(xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_STATE))); + xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_STATE)); } else { - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - atom(QXcbAtom::_NET_WM_STATE), XCB_ATOM_ATOM, 32, - atoms.count(), atoms.constData())); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + atom(QXcbAtom::_NET_WM_STATE), XCB_ATOM_ATOM, 32, + atoms.count(), atoms.constData()); } xcb_flush(xcb_connection()); } @@ -1256,67 +1248,48 @@ void QXcbWindow::changeNetWmState(bool set, xcb_atom_t one, xcb_atom_t two) event.data.data32[3] = 0; event.data.data32[4] = 0; - Q_XCB_CALL(xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&event)); + xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (const char *)&event); } -void QXcbWindow::setWindowState(Qt::WindowState state) +void QXcbWindow::setWindowState(Qt::WindowStates state) { if (state == m_windowState) return; - // unset old state - switch (m_windowState) { - case Qt::WindowMinimized: - Q_XCB_CALL(xcb_map_window(xcb_connection(), m_window)); - break; - case Qt::WindowMaximized: - changeNetWmState(false, - atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ), - atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT)); - break; - case Qt::WindowFullScreen: - changeNetWmState(false, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)); - break; - default: - break; + if ((m_windowState & Qt::WindowMinimized) && !(state & Qt::WindowMinimized)) { + xcb_map_window(xcb_connection(), m_window); + } else if (!(m_windowState & Qt::WindowMinimized) && (state & Qt::WindowMinimized)) { + xcb_client_message_event_t event; + + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.sequence = 0; + event.window = m_window; + event.type = atom(QXcbAtom::WM_CHANGE_STATE); + event.data.data32[0] = XCB_WM_STATE_ICONIC; + event.data.data32[1] = 0; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (const char *)&event); + m_minimized = true; } - // set new state - switch (state) { - case Qt::WindowMinimized: - { - xcb_client_message_event_t event; - - event.response_type = XCB_CLIENT_MESSAGE; - event.format = 32; - event.sequence = 0; - event.window = m_window; - event.type = atom(QXcbAtom::WM_CHANGE_STATE); - event.data.data32[0] = XCB_WM_STATE_ICONIC; - event.data.data32[1] = 0; - event.data.data32[2] = 0; - event.data.data32[3] = 0; - event.data.data32[4] = 0; - - Q_XCB_CALL(xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&event)); - } - break; - case Qt::WindowMaximized: - changeNetWmState(true, - atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ), + if ((m_windowState ^ state) & Qt::WindowMaximized) { + changeNetWmState(state & Qt::WindowMaximized, atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ), atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT)); - break; - case Qt::WindowFullScreen: - changeNetWmState(true, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)); - break; - case Qt::WindowNoState: - break; - default: - break; } - connection()->sync(); + if ((m_windowState ^ state) & Qt::WindowFullScreen) { + changeNetWmState(state & Qt::WindowFullScreen, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)); + } + connection()->sync(); m_windowState = state; } @@ -1388,10 +1361,10 @@ void QXcbWindow::updateNetWmStateBeforeMap() states |= NetWmStateBelow; } - if (window()->windowState() & Qt::WindowFullScreen) + if (window()->windowStates() & Qt::WindowFullScreen) states |= NetWmStateFullScreen; - if (window()->windowState() & Qt::WindowMaximized) { + if (window()->windowStates() & Qt::WindowMaximized) { states |= NetWmStateMaximizedHorz; states |= NetWmStateMaximizedVert; } @@ -1424,30 +1397,30 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp) if (m_netWmUserTimeWindow || isSupportedByWM) { if (!m_netWmUserTimeWindow) { m_netWmUserTimeWindow = xcb_generate_id(xcb_connection()); - Q_XCB_CALL(xcb_create_window(xcb_connection(), - XCB_COPY_FROM_PARENT, // depth -- same as root - m_netWmUserTimeWindow, // window id - m_window, // parent window id - -1, -1, 1, 1, - 0, // border width - XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class - m_visualId, // visual - 0, // value mask - 0)); // value list + xcb_create_window(xcb_connection(), + XCB_COPY_FROM_PARENT, // depth -- same as root + m_netWmUserTimeWindow, // window id + m_window, // parent window id + -1, -1, 1, 1, + 0, // border width + XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class + m_visualId, // visual + 0, // value mask + 0); // value list wid = m_netWmUserTimeWindow; xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW), XCB_ATOM_WINDOW, 32, 1, &m_netWmUserTimeWindow); xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME)); #ifndef QT_NO_DEBUG QByteArray ba("Qt NET_WM user time window"); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_netWmUserTimeWindow, - atom(QXcbAtom::_NET_WM_NAME), - atom(QXcbAtom::UTF8_STRING), - 8, - ba.length(), - ba.constData())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_netWmUserTimeWindow, + atom(QXcbAtom::_NET_WM_NAME), + atom(QXcbAtom::UTF8_STRING), + 8, + ba.length(), + ba.constData()); #endif } else if (!isSupportedByWM) { // WM no longer supports it, then we should remove the @@ -1521,26 +1494,27 @@ void QXcbWindow::setParent(const QPlatformWindow *parent) xcb_parent_id = xcbScreen()->root(); m_embedded = false; } - Q_XCB_CALL(xcb_reparent_window(xcb_connection(), xcb_window(), xcb_parent_id, topLeft.x(), topLeft.y())); + xcb_reparent_window(xcb_connection(), xcb_window(), xcb_parent_id, topLeft.x(), topLeft.y()); } void QXcbWindow::setWindowTitle(const QString &title) { QString fullTitle = formatWindowTitle(title, QString::fromUtf8(" \xe2\x80\x94 ")); // unicode character U+2014, EM DASH const QByteArray ba = std::move(fullTitle).toUtf8(); - Q_XCB_CALL(xcb_change_property(xcb_connection(), + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, atom(QXcbAtom::_NET_WM_NAME), atom(QXcbAtom::UTF8_STRING), 8, ba.length(), - ba.constData())); + ba.constData()); #if QT_CONFIG(xcb_xlib) - XTextProperty *text = qstringToXTP(DISPLAY_FROM_XCB(this), title); + Display *dpy = static_cast<Display *>(connection()->xlib_display()); + XTextProperty *text = qstringToXTP(dpy, title); if (text) - XSetWMName(DISPLAY_FROM_XCB(this), m_window, text); + XSetWMName(dpy, m_window, text); #endif xcb_flush(xcb_connection()); } @@ -1548,14 +1522,14 @@ void QXcbWindow::setWindowTitle(const QString &title) void QXcbWindow::setWindowIconText(const QString &title) { const QByteArray ba = title.toUtf8(); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_window, - atom(QXcbAtom::_NET_WM_ICON_NAME), - atom(QXcbAtom::UTF8_STRING), - 8, - ba.length(), - ba.constData())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_window, + atom(QXcbAtom::_NET_WM_ICON_NAME), + atom(QXcbAtom::UTF8_STRING), + 8, + ba.length(), + ba.constData()); } void QXcbWindow::setWindowIcon(const QIcon &icon) @@ -1585,18 +1559,18 @@ void QXcbWindow::setWindowIcon(const QIcon &icon) } if (!icon_data.isEmpty()) { - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_window, - atom(QXcbAtom::_NET_WM_ICON), - atom(QXcbAtom::CARDINAL), - 32, - icon_data.size(), - (unsigned char *) icon_data.data())); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_window, + atom(QXcbAtom::_NET_WM_ICON), + atom(QXcbAtom::CARDINAL), + 32, + icon_data.size(), + (unsigned char *) icon_data.data()); } else { - Q_XCB_CALL(xcb_delete_property(xcb_connection(), - m_window, - atom(QXcbAtom::_NET_WM_ICON))); + xcb_delete_property(xcb_connection(), + m_window, + atom(QXcbAtom::_NET_WM_ICON)); } } @@ -1604,14 +1578,14 @@ void QXcbWindow::raise() { const quint32 mask = XCB_CONFIG_WINDOW_STACK_MODE; const quint32 values[] = { XCB_STACK_MODE_ABOVE }; - Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, values)); + xcb_configure_window(xcb_connection(), m_window, mask, values); } void QXcbWindow::lower() { const quint32 mask = XCB_CONFIG_WINDOW_STACK_MODE; const quint32 values[] = { XCB_STACK_MODE_BELOW }; - Q_XCB_CALL(xcb_configure_window(xcb_connection(), m_window, mask, values)); + xcb_configure_window(xcb_connection(), m_window, mask, values); } // Adapt the geometry to match the WM expection with regards @@ -1705,9 +1679,11 @@ void QXcbWindow::requestActivateWindow() event.data.data32[3] = 0; event.data.data32[4] = 0; - Q_XCB_CALL(xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&event)); + xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (const char *)&event); } else { - Q_XCB_CALL(xcb_set_input_focus(xcb_connection(), XCB_INPUT_FOCUS_PARENT, m_window, connection()->time())); + xcb_set_input_focus(xcb_connection(), XCB_INPUT_FOCUS_PARENT, m_window, connection()->time()); } connection()->sync(); @@ -1751,15 +1727,11 @@ QXcbWindowFunctions::WmWindowTypes QXcbWindow::wmWindowTypes() const { QXcbWindowFunctions::WmWindowTypes result(0); - xcb_get_property_cookie_t get_cookie = - xcb_get_property_unchecked(xcb_connection(), 0, m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE), - XCB_ATOM_ATOM, 0, 1024); - - xcb_get_property_reply_t *reply = - xcb_get_property_reply(xcb_connection(), get_cookie, NULL); - + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), + 0, m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE), + XCB_ATOM_ATOM, 0, 1024); if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) { - const xcb_atom_t *types = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply)); + const xcb_atom_t *types = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get())); const xcb_atom_t *types_end = types + reply->length; for (; types != types_end; types++) { QXcbAtom::Atom type = connection()->qatom(*types); @@ -1813,7 +1785,6 @@ QXcbWindowFunctions::WmWindowTypes QXcbWindow::wmWindowTypes() const break; } } - free(reply); } return result; } @@ -1896,20 +1867,20 @@ void QXcbWindow::setWmWindowType(QXcbWindowFunctions::WmWindowTypes types, Qt::W atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL)); if (atoms.isEmpty()) { - Q_XCB_CALL(xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE))); + xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE)); } else { - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - atom(QXcbAtom::_NET_WM_WINDOW_TYPE), XCB_ATOM_ATOM, 32, - atoms.count(), atoms.constData())); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + atom(QXcbAtom::_NET_WM_WINDOW_TYPE), XCB_ATOM_ATOM, 32, + atoms.count(), atoms.constData()); } xcb_flush(xcb_connection()); } void QXcbWindow::setWmWindowRole(const QByteArray &role) { - Q_XCB_CALL(xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, - atom(QXcbAtom::WM_WINDOW_ROLE), XCB_ATOM_STRING, 8, - role.size(), role.constData())); + xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, + atom(QXcbAtom::WM_WINDOW_ROLE), XCB_ATOM_STRING, 8, + role.size(), role.constData()); } void QXcbWindow::setParentRelativeBackPixmapStatic(QWindow *window) @@ -1922,7 +1893,7 @@ void QXcbWindow::setParentRelativeBackPixmap() { const quint32 mask = XCB_CW_BACK_PIXMAP; const quint32 values[] = { XCB_BACK_PIXMAP_PARENT_RELATIVE }; - Q_XCB_CALL(xcb_change_window_attributes(xcb_connection(), m_window, mask, values)); + xcb_change_window_attributes(xcb_connection(), m_window, mask, values); } bool QXcbWindow::requestSystemTrayWindowDockStatic(const QWindow *window) @@ -2013,11 +1984,7 @@ void QXcbWindow::handleExposeEvent(const xcb_expose_event_t *event) { QRect rect(event->x, event->y, event->width, event->height); - if (m_exposeRegion.isEmpty()) - m_exposeRegion = rect; - else - m_exposeRegion |= rect; - + m_exposeRegion |= rect; bool pending = compressExposeEvent(m_exposeRegion); // if count is non-zero there are more expose events pending @@ -2099,13 +2066,11 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t * QPoint pos(event->x, event->y); if (!parent() && !fromSendEvent) { // Do not trust the position, query it instead. - xcb_translate_coordinates_cookie_t cookie = xcb_translate_coordinates(xcb_connection(), xcb_window(), - xcbScreen()->root(), 0, 0); - xcb_translate_coordinates_reply_t *reply = xcb_translate_coordinates_reply(xcb_connection(), cookie, NULL); + auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), + xcb_window(), xcbScreen()->root(), 0, 0); if (reply) { pos.setX(reply->dst_x); pos.setY(reply->dst_y); - free(reply); } } @@ -2114,14 +2079,6 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t * if (!newScreen) return; - // Persist the actual geometry so that QWindow::geometry() can - // be queried in the resize event. - QPlatformWindow::setGeometry(actualGeometry); - - // FIXME: In the case of the requestedGeometry not matching the actualGeometry due - // to e.g. the window manager applying restrictions to the geometry, the application - // will never see a move/resize event if the actualGeometry is the same as the current - // geometry, and may think the requested geometry was fulfilled. QWindowSystemInterface::handleGeometryChange(window(), actualGeometry); // QPlatformScreen::screen() is updated asynchronously, so we can't compare it @@ -2160,15 +2117,12 @@ QPoint QXcbWindow::mapToGlobal(const QPoint &pos) const return pos; QPoint ret; - xcb_translate_coordinates_cookie_t cookie = - xcb_translate_coordinates(xcb_connection(), xcb_window(), xcbScreen()->root(), - pos.x(), pos.y()); - xcb_translate_coordinates_reply_t *reply = - xcb_translate_coordinates_reply(xcb_connection(), cookie, NULL); + auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), + xcb_window(), xcbScreen()->root(), + pos.x(), pos.y()); if (reply) { ret.setX(reply->dst_x); ret.setY(reply->dst_y); - free(reply); } return ret; @@ -2180,15 +2134,12 @@ QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const return pos; QPoint ret; - xcb_translate_coordinates_cookie_t cookie = - xcb_translate_coordinates(xcb_connection(), xcbScreen()->root(), xcb_window(), - pos.x(), pos.y()); - xcb_translate_coordinates_reply_t *reply = - xcb_translate_coordinates_reply(xcb_connection(), cookie, NULL); + auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), + xcbScreen()->root(), xcb_window(), + pos.x(), pos.y()); if (reply) { ret.setX(reply->dst_x); ret.setY(reply->dst_y); - free(reply); } return ret; @@ -2240,16 +2191,24 @@ void QXcbWindow::handleButtonPressEvent(int event_x, int event_y, int root_x, in QPoint global(root_x, root_y); if (isWheel) { +#if QT_CONFIG(xinput2) if (!connection()->isAtLeastXI21()) { - // Logic borrowed from qapplication_x11.cpp - int delta = 120 * ((detail == 4 || detail == 6) ? 1 : -1); - bool hor = (((detail == 4 || detail == 5) - && (modifiers & Qt::AltModifier)) - || (detail == 6 || detail == 7)); - - QWindowSystemInterface::handleWheelEvent(window(), timestamp, - local, global, delta, hor ? Qt::Horizontal : Qt::Vertical, modifiers); +#endif + QPoint angleDelta; + if (detail == 4) + angleDelta.setY(120); + else if (detail == 5) + angleDelta.setY(-120); + else if (detail == 6) + angleDelta.setX(120); + else if (detail == 7) + angleDelta.setX(-120); + if (modifiers & Qt::AltModifier) + std::swap(angleDelta.rx(), angleDelta.ry()); + QWindowSystemInterface::handleWheelEvent(window(), timestamp, local, global, QPoint(), angleDelta, modifiers); +#if QT_CONFIG(xinput2) } +#endif return; } @@ -2269,7 +2228,7 @@ void QXcbWindow::handleButtonReleaseEvent(int event_x, int event_y, int root_x, return; } - if (connection()->buttons() == Qt::NoButton) + if (connection()->buttonState() == Qt::NoButton) connection()->setMousePressWindow(Q_NULLPTR); handleMouseEvent(timestamp, local, global, modifiers, source); @@ -2284,9 +2243,10 @@ static inline bool doCheckUnGrabAncestor(QXcbConnection *conn) * not pressed, otherwise (e.g. on Alt+Tab) it can igonre important enter/leave events. */ if (conn) { - const bool mouseButtonsPressed = (conn->buttons() != Qt::NoButton); -#ifdef XCB_USE_XINPUT22 - return mouseButtonsPressed || (conn->isAtLeastXI22() && conn->xi2MouseEvents()); + + const bool mouseButtonsPressed = (conn->buttonState() != Qt::NoButton); +#if QT_CONFIG(xinput2) + return mouseButtonsPressed || (conn->hasXInput2() && !conn->xi2MouseEventsDisabled()); #else return mouseButtonsPressed; #endif @@ -2335,7 +2295,8 @@ void QXcbWindow::handleEnterNotifyEvent(int event_x, int event_y, int root_x, in { connection()->setTime(timestamp); #ifdef XCB_USE_XINPUT21 - connection()->handleEnterEvent(); + // Updates scroll valuators, as user might have done some scrolling outside our X client. + connection()->xi2UpdateScrollingDevices(); #endif const QPoint global = QPoint(root_x, root_y); @@ -2378,7 +2339,7 @@ void QXcbWindow::handleMotionNotifyEvent(int event_x, int event_y, int root_x, i // "mousePressWindow" can be NULL i.e. if a window will be grabbed or unmapped, so set it again here. // Unset "mousePressWindow" when mouse button isn't pressed - in some cases the release event won't arrive. - const bool isMouseButtonPressed = (connection()->buttons() != Qt::NoButton); + const bool isMouseButtonPressed = (connection()->buttonState() != Qt::NoButton); const bool hasMousePressWindow = (connection()->mousePressWindow() != Q_NULLPTR); if (isMouseButtonPressed && !hasMousePressWindow) connection()->setMousePressWindow(this); @@ -2388,7 +2349,6 @@ void QXcbWindow::handleMotionNotifyEvent(int event_x, int event_y, int root_x, i handleMouseEvent(timestamp, local, global, modifiers, source); } -// Handlers for plain xcb events. Used only when XI 2.2 or newer is not available. void QXcbWindow::handleButtonPressEvent(const xcb_button_press_event_t *event) { Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state); @@ -2409,13 +2369,12 @@ void QXcbWindow::handleMotionNotifyEvent(const xcb_motion_notify_event_t *event) handleMotionNotifyEvent(event->event_x, event->event_y, event->root_x, event->root_y, modifiers, event->time); } -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) static inline int fixed1616ToInt(FP1616 val) { return int((qreal(val >> 16)) + (val & 0xFFFF) / (qreal)0xFFFF); } -// With XI 2.2+ press/release/motion comes here instead of the above handlers. void QXcbWindow::handleXIMouseEvent(xcb_ge_event_t *event, Qt::MouseEventSource source) { QXcbConnection *conn = connection(); @@ -2433,7 +2392,7 @@ void QXcbWindow::handleXIMouseEvent(xcb_ge_event_t *event, Qt::MouseEventSource return; } for (int i = 1; i <= 15; ++i) - conn->setButton(conn->translateMouseButton(i), XIMaskIsSet(buttonMask, i)); + conn->setButtonState(conn->translateMouseButton(i), XIMaskIsSet(buttonMask, i)); } const Qt::KeyboardModifiers modifiers = conn->keyboard()->translateModifiers(ev->mods.effective_mods); @@ -2457,13 +2416,13 @@ void QXcbWindow::handleXIMouseEvent(xcb_ge_event_t *event, Qt::MouseEventSource case XI_ButtonPress: if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "XI2 mouse press, button %d, time %d, source %s", button, ev->time, sourceName); - conn->setButton(button, true); + conn->setButtonState(button, true); handleButtonPressEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time, source); break; case XI_ButtonRelease: if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "XI2 mouse release, button %d, time %d, source %s", button, ev->time, sourceName); - conn->setButton(button, false); + conn->setButtonState(button, false); handleButtonReleaseEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time, source); break; case XI_Motion: @@ -2477,7 +2436,6 @@ void QXcbWindow::handleXIMouseEvent(xcb_ge_event_t *event, Qt::MouseEventSource } } -// With XI 2.2+ enter/leave comes here and are blocked in plain xcb events void QXcbWindow::handleXIEnterLeave(xcb_ge_event_t *event) { xXIEnterEvent *ev = reinterpret_cast<xXIEnterEvent *>(event); @@ -2497,12 +2455,14 @@ void QXcbWindow::handleXIEnterLeave(xcb_ge_event_t *event) case XI_Enter: { const int event_x = fixed1616ToInt(ev->event_x); const int event_y = fixed1616ToInt(ev->event_y); - qCDebug(lcQpaXInput, "XI2 mouse enter %d,%d, mode %d, detail %d, time %d", event_x, event_y, ev->mode, ev->detail, ev->time); + qCDebug(lcQpaXInputEvents, "XI2 mouse enter %d,%d, mode %d, detail %d, time %d", + event_x, event_y, ev->mode, ev->detail, ev->time); handleEnterNotifyEvent(event_x, event_y, root_x, root_y, ev->mode, ev->detail, ev->time); break; } case XI_Leave: - qCDebug(lcQpaXInput, "XI2 mouse leave, mode %d, detail %d, time %d", ev->mode, ev->detail, ev->time); + qCDebug(lcQpaXInputEvents, "XI2 mouse leave, mode %d, detail %d, time %d", + ev->mode, ev->detail, ev->time); connection()->keyboard()->updateXKBStateFromXI(&ev->mods, &ev->group); handleLeaveNotifyEvent(root_x, root_y, ev->mode, ev->detail, ev->time); break; @@ -2516,7 +2476,7 @@ void QXcbWindow::handleMouseEvent(xcb_timestamp_t time, const QPoint &local, con Qt::KeyboardModifiers modifiers, Qt::MouseEventSource source) { connection()->setTime(time); - QWindowSystemInterface::handleMouseEvent(window(), time, local, global, connection()->buttons(), modifiers, source); + QWindowSystemInterface::handleMouseEvent(window(), time, local, global, connection()->buttonState(), modifiers, source); } void QXcbWindow::handleEnterNotifyEvent(const xcb_enter_notify_event_t *event) @@ -2539,44 +2499,33 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev if (propertyDeleted) return; - Qt::WindowState newState = Qt::WindowNoState; - if (event->atom == atom(QXcbAtom::WM_STATE)) { // WM_STATE: Quick check for 'Minimize'. - const xcb_get_property_cookie_t get_cookie = - xcb_get_property(xcb_connection(), 0, m_window, atom(QXcbAtom::WM_STATE), - XCB_ATOM_ANY, 0, 1024); - - xcb_get_property_reply_t *reply = - xcb_get_property_reply(xcb_connection(), get_cookie, NULL); + Qt::WindowStates newState = Qt::WindowNoState; + if (event->atom == atom(QXcbAtom::WM_STATE)) { // WM_STATE: Quick check for 'Minimize'. + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), + 0, m_window, atom(QXcbAtom::WM_STATE), + XCB_ATOM_ANY, 0, 1024); if (reply && reply->format == 32 && reply->type == atom(QXcbAtom::WM_STATE)) { - const quint32 *data = (const quint32 *)xcb_get_property_value(reply); - if (reply->length != 0) { - if (data[0] == XCB_WM_STATE_ICONIC - || (data[0] == XCB_WM_STATE_WITHDRAWN - && m_lastWindowStateEvent == Qt::WindowMinimized)) { - newState = Qt::WindowMinimized; - } - } + const quint32 *data = (const quint32 *)xcb_get_property_value(reply.get()); + if (reply->length != 0) + m_minimized = (data[0] == XCB_WM_STATE_ICONIC + || (data[0] == XCB_WM_STATE_WITHDRAWN && m_minimized)); } - free(reply); - } else { // _NET_WM_STATE can't change minimized state - if (m_lastWindowStateEvent == Qt::WindowMinimized) - newState = Qt::WindowMinimized; - } - - if (newState != Qt::WindowMinimized) { // Something else changed, get _NET_WM_STATE. - const NetWmStates states = netWmStates(); - if (states & NetWmStateFullScreen) - newState = Qt::WindowFullScreen; - else if ((states & NetWmStateMaximizedHorz) && (states & NetWmStateMaximizedVert)) - newState = Qt::WindowMaximized; } + if (m_minimized) + newState = Qt::WindowMinimized; + + const NetWmStates states = netWmStates(); + if (states & NetWmStateFullScreen) + newState |= Qt::WindowFullScreen; + if ((states & NetWmStateMaximizedHorz) && (states & NetWmStateMaximizedVert)) + newState |= Qt::WindowMaximized; // Send Window state, compress events in case other flags (modality, etc) are changed. if (m_lastWindowStateEvent != newState) { QWindowSystemInterface::handleWindowStateChanged(window(), newState); m_lastWindowStateEvent = newState; m_windowState = newState; - if (m_windowState == Qt::WindowMinimized && connection()->mouseGrabber() == this) + if ((m_windowState & Qt::WindowMinimized) && connection()->mouseGrabber() == this) connection()->setMouseGrabber(Q_NULLPTR); } return; @@ -2611,7 +2560,7 @@ void QXcbWindow::updateSyncRequestCounter() return; } if (m_usingSyncProtocol && (m_syncValue.lo != 0 || m_syncValue.hi != 0)) { - Q_XCB_CALL(xcb_sync_set_counter(xcb_connection(), m_syncCounter, m_syncValue)); + xcb_sync_set_counter(xcb_connection(), m_syncCounter, m_syncValue); xcb_flush(xcb_connection()); m_syncValue.lo = 0; @@ -2635,21 +2584,20 @@ bool QXcbWindow::setKeyboardGrabEnabled(bool grab) xcb_ungrab_keyboard(xcb_connection(), XCB_TIME_CURRENT_TIME); return true; } - xcb_grab_keyboard_cookie_t cookie = xcb_grab_keyboard(xcb_connection(), false, - m_window, XCB_TIME_CURRENT_TIME, - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); - xcb_grab_keyboard_reply_t *reply = xcb_grab_keyboard_reply(xcb_connection(), cookie, NULL); - bool result = !(!reply || reply->status != XCB_GRAB_STATUS_SUCCESS); - free(reply); - return result; + + auto reply = Q_XCB_REPLY(xcb_grab_keyboard, xcb_connection(), false, + m_window, XCB_TIME_CURRENT_TIME, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + return reply && reply->status == XCB_GRAB_STATUS_SUCCESS; } bool QXcbWindow::setMouseGrabEnabled(bool grab) { if (!grab && connection()->mouseGrabber() == this) connection()->setMouseGrabber(Q_NULLPTR); -#ifdef XCB_USE_XINPUT22 - if (connection()->isAtLeastXI22() && connection()->xi2MouseEvents()) { + +#if QT_CONFIG(xinput2) + if (connection()->hasXInput2() && !connection()->xi2MouseEventsDisabled()) { bool result = connection()->xi2SetMouseGrabEnabled(m_window, grab); if (grab && result) connection()->setMouseGrabber(this); @@ -2663,16 +2611,16 @@ bool QXcbWindow::setMouseGrabEnabled(bool grab) xcb_ungrab_pointer(xcb_connection(), XCB_TIME_CURRENT_TIME); return true; } - xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer(xcb_connection(), false, m_window, - (XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE - | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_ENTER_WINDOW - | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION), - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, - XCB_WINDOW_NONE, XCB_CURSOR_NONE, - XCB_TIME_CURRENT_TIME); - xcb_grab_pointer_reply_t *reply = xcb_grab_pointer_reply(xcb_connection(), cookie, NULL); - bool result = !(!reply || reply->status != XCB_GRAB_STATUS_SUCCESS); - free(reply); + + auto reply = Q_XCB_REPLY(xcb_grab_pointer, xcb_connection(), + false, m_window, + (XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE + | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_ENTER_WINDOW + | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION), + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, + XCB_WINDOW_NONE, XCB_CURSOR_NONE, + XCB_TIME_CURRENT_TIME); + bool result = reply && reply->status == XCB_GRAB_STATUS_SUCCESS; if (result) connection()->setMouseGrabber(this); return result; @@ -2780,8 +2728,7 @@ void QXcbWindow::sendXEmbedMessage(xcb_window_t window, quint32 message, event.data.data32[2] = detail; event.data.data32[3] = data1; event.data.data32[4] = data2; - Q_XCB_CALL(xcb_send_event(xcb_connection(), false, window, - XCB_EVENT_MASK_NO_EVENT, (const char *)&event)); + xcb_send_event(xcb_connection(), false, window, XCB_EVENT_MASK_NO_EVENT, (const char *)&event); } static bool activeWindowChangeQueued(const QWindow *window) @@ -2803,12 +2750,12 @@ void QXcbWindow::handleXEmbedMessage(const xcb_client_message_event_t *event) case XEMBED_WINDOW_DEACTIVATE: break; case XEMBED_EMBEDDED_NOTIFY: - Q_XCB_CALL(xcb_map_window(xcb_connection(), m_window)); + xcb_map_window(xcb_connection(), m_window); xcbScreen()->windowShown(this); // Without Qt::WA_TranslucentBackground, we use a ParentRelative BackPixmap. // Clear the whole tray icon window to its background color as early as possible // so that we can get a clean result from grabWindow() later. - Q_XCB_CALL(xcb_clear_area(xcb_connection(), false, m_window, 0, 0, geometry().width(), geometry().height())); + xcb_clear_area(xcb_connection(), false, m_window, 0, 0, geometry().width(), geometry().height()); xcb_flush(xcb_connection()); break; case XEMBED_FOCUS_IN: @@ -2855,14 +2802,14 @@ void QXcbWindow::setOpacity(qreal level) quint32 value = qRound64(qBound(qreal(0), level, qreal(1)) * 0xffffffff); - Q_XCB_CALL(xcb_change_property(xcb_connection(), - XCB_PROP_MODE_REPLACE, - m_window, - atom(QXcbAtom::_NET_WM_WINDOW_OPACITY), - XCB_ATOM_CARDINAL, - 32, - 1, - (uchar *)&value)); + xcb_change_property(xcb_connection(), + XCB_PROP_MODE_REPLACE, + m_window, + atom(QXcbAtom::_NET_WM_WINDOW_OPACITY), + XCB_ATOM_CARDINAL, + 32, + 1, + (uchar *)&value); } void QXcbWindow::setMask(const QRegion ®ion) diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h index f38343b6c2..1ce9b0a42f 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.h +++ b/src/plugins/platforms/xcb/qxcbwindow.h @@ -82,7 +82,7 @@ public: void setVisible(bool visible) override; void setWindowFlags(Qt::WindowFlags flags) override; - void setWindowState(Qt::WindowState state) override; + void setWindowState(Qt::WindowStates state) override; WId winId() const override; void setParent(const QPlatformWindow *window) override; @@ -138,7 +138,7 @@ public: void handleFocusInEvent(const xcb_focus_in_event_t *event) override; void handleFocusOutEvent(const xcb_focus_out_event_t *event) override; void handlePropertyNotifyEvent(const xcb_property_notify_event_t *event) override; -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) void handleXIMouseEvent(xcb_ge_event_t *, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized) override; void handleXIEnterLeave(xcb_ge_event_t *) override; #endif @@ -244,7 +244,7 @@ protected: xcb_sync_int64_t m_syncValue; xcb_sync_counter_t m_syncCounter = 0; - Qt::WindowState m_windowState = Qt::WindowNoState; + Qt::WindowStates m_windowState = Qt::WindowNoState; xcb_gravity_t m_gravity = XCB_GRAVITY_STATIC; @@ -254,6 +254,7 @@ protected: bool m_deferredActivation = false; bool m_embedded = false; bool m_alertState = false; + bool m_minimized = false; xcb_window_t m_netWmUserTimeWindow = XCB_NONE; QSurfaceFormat m_format; @@ -265,7 +266,8 @@ protected: QSize m_oldWindowSize; xcb_visualid_t m_visualId = 0; - int m_lastWindowStateEvent = -1; + // Last sent state. Initialized to an invalid state, on purpose. + Qt::WindowStates m_lastWindowStateEvent = Qt::WindowActive; enum SyncState { NoSyncNeeded, diff --git a/src/plugins/platforms/xcb/qxcbwmsupport.cpp b/src/plugins/platforms/xcb/qxcbwmsupport.cpp index 470f021314..2619892b19 100644 --- a/src/plugins/platforms/xcb/qxcbwmsupport.cpp +++ b/src/plugins/platforms/xcb/qxcbwmsupport.cpp @@ -66,16 +66,15 @@ void QXcbWMSupport::updateNetWMAtoms() int offset = 0; int remaining = 0; do { - xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, root, atom(QXcbAtom::_NET_SUPPORTED), XCB_ATOM_ATOM, offset, 1024); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, NULL); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, root, atom(QXcbAtom::_NET_SUPPORTED), XCB_ATOM_ATOM, offset, 1024); if (!reply) break; remaining = 0; if (reply->type == XCB_ATOM_ATOM && reply->format == 32) { - int len = xcb_get_property_value_length(reply)/sizeof(xcb_atom_t); - xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply); + int len = xcb_get_property_value_length(reply.get())/sizeof(xcb_atom_t); + xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get()); int s = net_wm_atoms.size(); net_wm_atoms.resize(s + len); memcpy(net_wm_atoms.data() + s, atoms, len*sizeof(xcb_atom_t)); @@ -83,8 +82,6 @@ void QXcbWMSupport::updateNetWMAtoms() remaining = reply->bytes_after; offset += len; } - - free(reply); } while (remaining > 0); } @@ -100,16 +97,16 @@ void QXcbWMSupport::updateVirtualRoots() int offset = 0; int remaining = 0; do { - xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, root, atom(QXcbAtom::_NET_VIRTUAL_ROOTS), XCB_ATOM_WINDOW, offset, 1024); - xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, NULL); + auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), + false, root, atom(QXcbAtom::_NET_VIRTUAL_ROOTS), XCB_ATOM_WINDOW, offset, 1024); if (!reply) break; remaining = 0; if (reply->type == XCB_ATOM_WINDOW && reply->format == 32) { - int len = xcb_get_property_value_length(reply)/sizeof(xcb_window_t); - xcb_window_t *roots = (xcb_window_t *)xcb_get_property_value(reply); + int len = xcb_get_property_value_length(reply.get())/sizeof(xcb_window_t); + xcb_window_t *roots = (xcb_window_t *)xcb_get_property_value(reply.get()); int s = net_virtual_roots.size(); net_virtual_roots.resize(s + len); memcpy(net_virtual_roots.data() + s, roots, len*sizeof(xcb_window_t)); @@ -118,10 +115,10 @@ void QXcbWMSupport::updateVirtualRoots() offset += len; } - free(reply); } while (remaining > 0); -#ifdef Q_XCB_DEBUG +//#define VIRTUAL_ROOTS_DEBUG +#ifdef VIRTUAL_ROOTS_DEBUG qDebug("======== updateVirtualRoots"); for (int i = 0; i < net_virtual_roots.size(); ++i) qDebug() << connection()->atomName(net_virtual_roots.at(i)); diff --git a/src/plugins/platforms/xcb/qxcbxsettings.cpp b/src/plugins/platforms/xcb/qxcbxsettings.cpp index 88933c6c22..bd398ea049 100644 --- a/src/plugins/platforms/xcb/qxcbxsettings.cpp +++ b/src/plugins/platforms/xcb/qxcbxsettings.cpp @@ -106,26 +106,23 @@ public: QByteArray settings; xcb_atom_t _xsettings_atom = screen->connection()->atom(QXcbAtom::_XSETTINGS_SETTINGS); while (1) { - xcb_get_property_cookie_t get_prop_cookie = - xcb_get_property_unchecked(screen->xcb_connection(), + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, + screen->xcb_connection(), false, x_settings_window, _xsettings_atom, _xsettings_atom, offset/4, 8192); - xcb_get_property_reply_t *reply = xcb_get_property_reply(screen->xcb_connection(), get_prop_cookie, NULL); bool more = false; if (!reply) return settings; - const auto property_value_length = xcb_get_property_value_length(reply); - settings.append(static_cast<const char *>(xcb_get_property_value(reply)), property_value_length); + const auto property_value_length = xcb_get_property_value_length(reply.get()); + settings.append(static_cast<const char *>(xcb_get_property_value(reply.get())), property_value_length); offset += property_value_length; more = reply->bytes_after != 0; - free(reply); - if (!more) break; } @@ -228,34 +225,24 @@ QXcbXSettings::QXcbXSettings(QXcbVirtualDesktop *screen) { QByteArray settings_atom_for_screen("_XSETTINGS_S"); settings_atom_for_screen.append(QByteArray::number(screen->number())); - xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom(screen->xcb_connection(), - true, - settings_atom_for_screen.length(), - settings_atom_for_screen.constData()); - xcb_generic_error_t *error = 0; - xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(screen->xcb_connection(),atom_cookie,&error); - if (error) { - free(error); + auto atom_reply = Q_XCB_REPLY(xcb_intern_atom, + screen->xcb_connection(), + true, + settings_atom_for_screen.length(), + settings_atom_for_screen.constData()); + if (!atom_reply) return; - } - xcb_atom_t selection_owner_atom = atom_reply->atom; - free(atom_reply); - xcb_get_selection_owner_cookie_t selection_cookie = - xcb_get_selection_owner(screen->xcb_connection(), selection_owner_atom); + xcb_atom_t selection_owner_atom = atom_reply->atom; - xcb_get_selection_owner_reply_t *selection_result = - xcb_get_selection_owner_reply(screen->xcb_connection(), selection_cookie, &error); - if (error) { - free(error); + auto selection_result = Q_XCB_REPLY(xcb_get_selection_owner, + screen->xcb_connection(), selection_owner_atom); + if (!selection_result) return; - } d_ptr->x_settings_window = selection_result->owner; - free(selection_result); - if (!d_ptr->x_settings_window) { + if (!d_ptr->x_settings_window) return; - } const uint32_t event = XCB_CW_EVENT_MASK; const uint32_t event_mask[] = { XCB_EVENT_MASK_STRUCTURE_NOTIFY|XCB_EVENT_MASK_PROPERTY_CHANGE }; diff --git a/src/plugins/platforms/xcb/xcb_qpa_lib.pro b/src/plugins/platforms/xcb/xcb_qpa_lib.pro index 55007b43a7..00cce13fd0 100644 --- a/src/plugins/platforms/xcb/xcb_qpa_lib.pro +++ b/src/plugins/platforms/xcb/xcb_qpa_lib.pro @@ -5,11 +5,14 @@ DEFINES += QT_NO_FOREACH QT += \ core-private gui-private \ service_support-private theme_support-private \ - eventdispatcher_support-private fontdatabase_support-private + eventdispatcher_support-private fontdatabase_support-private \ + edid_support-private qtHaveModule(linuxaccessibility_support-private): \ QT += linuxaccessibility_support-private +qtConfig(vulkan): QT += vulkan_support-private + SOURCES = \ qxcbclipboard.cpp \ qxcbconnection.cpp \ @@ -65,6 +68,17 @@ qtConfig(xcb-sm) { } include(gl_integrations/gl_integrations.pri) +include(nativepainting/nativepainting.pri) + +qtConfig(vulkan) { + SOURCES += \ + qxcbvulkaninstance.cpp \ + qxcbvulkanwindow.cpp + + HEADERS += \ + qxcbvulkaninstance.h \ + qxcbvulkanwindow.h +} !qtConfig(system-xcb) { QMAKE_USE += xcb-static xcb |