diff options
Diffstat (limited to 'src/plugins/platforms')
369 files changed, 26992 insertions, 10914 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/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..13d41bea99 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -59,6 +59,7 @@ #include "qandroideventdispatcher.h" #include <android/api-level.h> +#include <QtCore/qthread.h> #include <QtCore/private/qjnihelpers_p.h> #include <QtCore/private/qjni_p.h> #include <QtGui/private/qguiapplication_p.h> @@ -99,7 +100,6 @@ extern "C" typedef int (*Main)(int, char **); //use the standard main method to static Main m_main = nullptr; static void *m_mainLibraryHnd = nullptr; static QList<QByteArray> m_applicationParams; -pthread_t m_qtAppThread = 0; static sem_t m_exitSemaphore, m_terminateSemaphore; QHash<int, AndroidSurfaceClient *> m_surfaces; @@ -441,57 +441,10 @@ namespace QtAndroid } // namespace QtAndroid -static jboolean startQtAndroidPlugin(JNIEnv* /*env*/, jobject /*object*//*, jobject applicationAssetManager*/) +static jboolean startQtAndroidPlugin(JNIEnv *env, jobject /*object*/, jstring paramsString, jstring environmentString) { m_androidPlatformIntegration = nullptr; m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler(); - return true; -} - -static void *startMainMethod(void */*data*/) -{ - { - JNIEnv* env = nullptr; - JavaVMAttachArgs args; - args.version = JNI_VERSION_1_6; - args.name = "QtMainThread"; - args.group = NULL; - JavaVM *vm = QtAndroidPrivate::javaVM(); - if (vm != 0) - vm->AttachCurrentThread(&env, &args); - } - - QVarLengthArray<const char *> params(m_applicationParams.size()); - for (int i = 0; i < m_applicationParams.size(); i++) - params[i] = static_cast<const char *>(m_applicationParams[i].constData()); - - int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data())); - - if (m_mainLibraryHnd) { - int res = dlclose(m_mainLibraryHnd); - if (res < 0) - qWarning() << "dlclose failed:" << dlerror(); - } - - if (m_applicationClass) - QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "quitApp", "()V"); - - // All attached threads should be detached before returning from this function. - JavaVM *vm = QtAndroidPrivate::javaVM(); - if (vm != 0) - vm->DetachCurrentThread(); - - sem_post(&m_terminateSemaphore); - sem_wait(&m_exitSemaphore); - sem_destroy(&m_exitSemaphore); - - // We must call exit() to ensure that all global objects will be destructed - exit(ret); - return 0; -} - -static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring paramsString, jstring environmentString) -{ m_mainLibraryHnd = nullptr; { // Set env. vars const char *nativeString = env->GetStringUTFChars(environmentString, 0); @@ -540,7 +493,54 @@ 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; + return true; +} + +static void waitForServiceSetup(JNIEnv *env, jclass /*clazz*/) +{ + Q_UNUSED(env); + // The service must wait until the QCoreApplication starts otherwise onBind will be + // called too early + if (m_serviceObject) + QtAndroidPrivate::waitForServiceSetup(); +} + +static jboolean startQtApplication(JNIEnv */*env*/, jclass /*clazz*/) +{ + { + JNIEnv* env = nullptr; + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_6; + args.name = "QtMainThread"; + args.group = NULL; + JavaVM *vm = QtAndroidPrivate::javaVM(); + if (vm != 0) + vm->AttachCurrentThread(&env, &args); + } + + QVarLengthArray<const char *> params(m_applicationParams.size()); + for (int i = 0; i < m_applicationParams.size(); i++) + params[i] = static_cast<const char *>(m_applicationParams[i].constData()); + + int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data())); + + if (m_mainLibraryHnd) { + int res = dlclose(m_mainLibraryHnd); + if (res < 0) + qWarning() << "dlclose failed:" << dlerror(); + } + + if (m_applicationClass) { + qWarning("exit app 0"); + QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "quitApp", "()V"); + } + + sem_post(&m_terminateSemaphore); + sem_wait(&m_exitSemaphore); + sem_destroy(&m_exitSemaphore); + + // We must call exit() to ensure that all global objects will be destructed + exit(ret); } static void quitQtCoreApplication(JNIEnv *env, jclass /*clazz*/) @@ -586,7 +586,6 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/) if (!QAndroidEventDispatcherStopper::instance()->stopped()) { sem_post(&m_exitSemaphore); - pthread_join(m_qtAppThread, nullptr); } } @@ -745,19 +744,26 @@ 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}, + {"startQtAndroidPlugin", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)startQtAndroidPlugin}, + {"startQtApplication", "()V", (void *)startQtApplication}, {"quitQtAndroidPlugin", "()V", (void *)quitQtAndroidPlugin}, {"quitQtCoreApplication", "()V", (void *)quitQtCoreApplication}, {"terminateQt", "()V", (void *)terminateQt}, + {"waitForServiceSetup", "()V", (void *)waitForServiceSetup}, {"setDisplayMetrics", "(IIIIDDDD)V", (void *)setDisplayMetrics}, {"setSurface", "(ILjava/lang/Object;II)V", (void *)setSurface}, {"updateWindow", "()V", (void *)updateWindow}, {"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) \ @@ -871,7 +877,6 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) void *venv; } UnionJNIEnvToVoid; - __android_log_print(ANDROID_LOG_INFO, "Qt", "qt start"); UnionJNIEnvToVoid uenv; uenv.venv = nullptr; m_javaVM = nullptr; @@ -893,5 +898,10 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); m_javaVM = vm; + // attach qt main thread data to this thread + QObject threadSetter; + if (threadSetter.thread()) + threadSetter.thread()->setObjectName("QtMainLoopThread"); + __android_log_print(ANDROID_LOG_INFO, "Qt", "qt started"); return JNI_VERSION_1_4; } 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..7dc8bb8080 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.cpp +++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp @@ -59,6 +59,8 @@ #include <QtGui/QWindow> #include <QtGui/private/qwindow_p.h> +#include <vector> + QT_BEGIN_NAMESPACE #ifdef QANDROIDPLATFORMSCREEN_DEBUG @@ -85,7 +87,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 +103,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 +136,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 +219,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) @@ -366,8 +378,7 @@ void QAndroidPlatformScreen::doRedraw() || !window->isRaster()) continue; - const QVector<QRect> visibleRects = visibleRegion.rects(); - for (const QRect &rect : visibleRects) { + for (const QRect &rect : std::vector<QRect>(visibleRegion.begin(), visibleRegion.end())) { QRect targetRect = window->geometry(); targetRect &= 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/bsdfb/qbsdfbscreen.cpp b/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp index 067a26a7b1..d752b539a0 100644 --- a/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp +++ b/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp @@ -247,8 +247,7 @@ QRegion QBsdFbScreen::doRedraw() if (!m_blitter) m_blitter.reset(new QPainter(&m_onscreenImage)); - const auto rects = touched.rects(); - for (const QRect &rect : rects) + for (const QRect &rect : touched) m_blitter->drawImage(rect, mScreenImage, rect); return touched; } diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 5a00e1b7ec..2694cb3607 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -2,16 +2,19 @@ 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 \ qcocoaeventdispatcher.mm \ qcocoaapplicationdelegate.mm \ qcocoaapplication.mm \ + qcocoansmenu.mm \ qcocoamenu.mm \ qcocoamenuitem.mm \ qcocoamenubar.mm \ @@ -34,15 +37,18 @@ 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 \ qcocoaapplicationdelegate.h \ qcocoaapplication.h \ + qcocoansmenu.h \ qcocoamenu.h \ qcocoamenuitem.h \ qcocoamenubar.h \ @@ -72,12 +78,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.h b/src/plugins/platforms/cocoa/qcocoaaccessibility.h index e7957276c2..e456364555 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.h +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.h @@ -53,10 +53,10 @@ class QCocoaAccessibility : public QPlatformAccessibility public: QCocoaAccessibility(); ~QCocoaAccessibility(); - void notifyAccessibilityUpdate(QAccessibleEvent *event) Q_DECL_OVERRIDE; - void setRootObject(QObject *o) Q_DECL_OVERRIDE; - void initialize() Q_DECL_OVERRIDE; - void cleanup() Q_DECL_OVERRIDE; + void notifyAccessibilityUpdate(QAccessibleEvent *event) override; + void setRootObject(QObject *o) override; + void initialize() override; + void cleanup() override; }; namespace QCocoaAccessible { 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/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index 7e6bae127c..df2336d08b 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -40,6 +40,7 @@ #include "qcocoaaccessibility.h" #include "qcocoahelpers.h" #include "qcocoawindow.h" +#include "qcocoascreen.h" #include "private/qaccessiblecache_p.h" #include <QtAccessibilitySupport/private/qaccessiblebridgeutils_p.h> #include <QtGui/qaccessible.h> @@ -298,9 +299,9 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of // We're in the same top level element as our parent. return [[self parentElement] accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute]; } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { - QPoint qtPosition = iface->rect().topLeft(); - QSize qtSize = iface->rect().size(); - return [NSValue valueWithPoint: NSMakePoint(qtPosition.x(), qt_mac_flipYCoordinate(qtPosition.y() + qtSize.height()))]; + // The position in points of the element's lower-left corner in screen-relative coordinates + QPointF qtPosition = QRectF(iface->rect()).bottomLeft(); + return [NSValue valueWithPoint:QCocoaScreen::mapToNative(qtPosition)]; } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { QSize qtSize = iface->rect().size(); return [NSValue valueWithSize: NSMakeSize(qtSize.width(), qtSize.height())]; @@ -430,7 +431,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of if ([attribute isEqualToString: NSAccessibilityBoundsForRangeParameterizedAttribute]) { NSRange range = [parameter rangeValue]; QRect firstRect = iface->textInterface()->characterRect(range.location); - QRect rect; + QRectF rect; if (range.length > 0) { NSUInteger position = range.location + range.length - 1; if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n")) @@ -441,15 +442,14 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of rect = firstRect; rect.setWidth(1); } - return [NSValue valueWithRect: NSMakeRect((CGFloat) rect.x(),(CGFloat) qt_mac_flipYCoordinate(rect.y() + rect.height()), rect.width(), rect.height())]; + return [NSValue valueWithRect:QCocoaScreen::mapToNative(rect)]; } if ([attribute isEqualToString: NSAccessibilityAttributedStringForRangeParameterizedAttribute]) { NSRange range = [parameter rangeValue]; QString text = iface->textInterface()->text(range.location, range.location + range.length); return [[NSAttributedString alloc] initWithString:text.toNSString()]; } else if ([attribute isEqualToString: NSAccessibilityRangeForPositionParameterizedAttribute]) { - NSPoint nsPoint = [parameter pointValue]; - QPoint point(static_cast<int>(nsPoint.x), static_cast<int>(qt_mac_flipYCoordinate(nsPoint.y))); + QPoint point = QCocoaScreen::mapFromNative([parameter pointValue]).toPoint(); int offset = iface->textInterface()->offsetAtPoint(point); return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(offset), 1)]; } else if ([attribute isEqualToString: NSAccessibilityStyleRangeForIndexParameterizedAttribute]) { @@ -566,8 +566,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return NSAccessibilityUnignoredAncestor(self); } - int y = qt_mac_flipYCoordinate(point.y); - QAccessibleInterface *childInterface = iface->childAt(point.x, y); + QPointF screenPoint = QCocoaScreen::mapFromNative(point); + QAccessibleInterface *childInterface = iface->childAt(screenPoint.x(), screenPoint.y()); // No child found, meaning we hit this element. if (!childInterface || !childInterface->isValid()) return NSAccessibilityUnignoredAncestor(self); @@ -575,7 +575,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of // find the deepest child at the point QAccessibleInterface *childOfChildInterface = 0; do { - childOfChildInterface = childInterface->childAt(point.x, y); + childOfChildInterface = childInterface->childAt(screenPoint.x(), screenPoint.y()); if (childOfChildInterface && childOfChildInterface->isValid()) childInterface = childOfChildInterface; } while (childOfChildInterface && childOfChildInterface->isValid()); diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 03148c769b..a94e0dc517 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -86,16 +86,21 @@ QT_USE_NAMESPACE -QT_BEGIN_NAMESPACE -static QCocoaApplicationDelegate *sharedCocoaApplicationDelegate = nil; +@implementation QCocoaApplicationDelegate -static void cleanupCocoaApplicationDelegate() ++ (instancetype)sharedDelegate { - [sharedCocoaApplicationDelegate release]; + static QCocoaApplicationDelegate *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[self alloc] init]; + atexit_b(^{ + [shared release]; + shared = nil; + }); + }); + return shared; } -QT_END_NAMESPACE - -@implementation QCocoaApplicationDelegate - (id)init { @@ -120,7 +125,6 @@ QT_END_NAMESPACE - (void)dealloc { - sharedCocoaApplicationDelegate = nil; [dockMenu release]; if (reflectionDelegate) { [[NSApplication sharedApplication] setDelegate:reflectionDelegate]; @@ -131,27 +135,6 @@ QT_END_NAMESPACE [super dealloc]; } -+ (id)allocWithZone:(NSZone *)zone -{ - @synchronized(self) { - if (sharedCocoaApplicationDelegate == nil) { - sharedCocoaApplicationDelegate = [super allocWithZone:zone]; - qAddPostRoutine(cleanupCocoaApplicationDelegate); - return sharedCocoaApplicationDelegate; - } - } - return nil; -} - -+ (QCocoaApplicationDelegate *)sharedDelegate -{ - @synchronized(self) { - if (sharedCocoaApplicationDelegate == nil) - [[self alloc] init]; - } - return [[sharedCocoaApplicationDelegate retain] autorelease]; -} - - (void)setDockMenu:(NSMenu*)newMenu { [newMenu retain]; @@ -284,7 +267,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..b4cd506513 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 @@ -50,10 +52,12 @@ public: QCocoaBackingStore(QWindow *window); ~QCocoaBackingStore(); - void flush(QWindow *, const QRegion &, const QPoint &) Q_DECL_OVERRIDE; + void flush(QWindow *, const QRegion &, const QPoint &) override; private: - QImage::Format format() const Q_DECL_OVERRIDE; + bool windowHasUnifiedToolbar() const; + QImage::Format format() const 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/qcocoaclipboard.h b/src/plugins/platforms/cocoa/qcocoaclipboard.h index cab2cfaa91..a78d8f4187 100644 --- a/src/plugins/platforms/cocoa/qcocoaclipboard.h +++ b/src/plugins/platforms/cocoa/qcocoaclipboard.h @@ -56,10 +56,10 @@ class QCocoaClipboard : public QObject, public QPlatformClipboard public: QCocoaClipboard(); - QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE; - void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE; - bool supportsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE; - bool ownsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE; + QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; + void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; + bool supportsMode(QClipboard::Mode mode) const override; + bool ownsMode(QClipboard::Mode mode) const override; private Q_SLOTS: void handleApplicationStateChanged(Qt::ApplicationState state); diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h index 133efd6db8..01ac7e181d 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h @@ -55,12 +55,12 @@ public: QCocoaColorDialogHelper(); ~QCocoaColorDialogHelper(); - void exec() Q_DECL_OVERRIDE; - bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) Q_DECL_OVERRIDE; - void hide() Q_DECL_OVERRIDE; + void exec() override; + bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; + void hide() override; - void setCurrentColor(const QColor&) Q_DECL_OVERRIDE; - QColor currentColor() const Q_DECL_OVERRIDE; + void setCurrentColor(const QColor&) override; + QColor currentColor() const override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoacursor.h b/src/plugins/platforms/cocoa/qcocoacursor.h index d1586660a4..58b9ef2151 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.h +++ b/src/plugins/platforms/cocoa/qcocoacursor.h @@ -53,9 +53,9 @@ public: QCocoaCursor(); ~QCocoaCursor(); - void changeCursor(QCursor *cursor, QWindow *window) Q_DECL_OVERRIDE; - QPoint pos() const Q_DECL_OVERRIDE; - void setPos(const QPoint &position) Q_DECL_OVERRIDE; + void changeCursor(QCursor *cursor, QWindow *window) override; + QPoint pos() const override; + void setPos(const QPoint &position) override; private: QHash<Qt::CursorShape, NSCursor *> m_cursors; NSCursor *convertCursor(QCursor *cursor); diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index 99a136d384..c021128e4c 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.mm @@ -39,6 +39,7 @@ #include "qcocoacursor.h" #include "qcocoawindow.h" +#include "qcocoascreen.h" #include "qcocoahelpers.h" #include <QtGui/private/qcoregraphics_p.h> @@ -70,7 +71,7 @@ void QCocoaCursor::changeCursor(QCursor *cursor, QWindow *window) QPoint QCocoaCursor::pos() const { - return qt_mac_flipPoint([NSEvent mouseLocation]).toPoint(); + return QCocoaScreen::mapFromNative([NSEvent mouseLocation]).toPoint(); } void QCocoaCursor::setPos(const QPoint &position) @@ -86,7 +87,7 @@ void QCocoaCursor::setPos(const QPoint &position) NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) { - if (cursor == Q_NULLPTR) + if (cursor == nullptr) return 0; const Qt::CursorShape newShape = cursor->shape(); diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h index 9ebb090989..dc0cc17dfb 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.h +++ b/src/plugins/platforms/cocoa/qcocoadrag.h @@ -55,11 +55,11 @@ public: QCocoaDrag(); ~QCocoaDrag(); - QMimeData *platformDropData() Q_DECL_OVERRIDE; - Qt::DropAction drag(QDrag *m_drag) Q_DECL_OVERRIDE; + QMimeData *dragMimeData(); + Qt::DropAction drag(QDrag *m_drag) override; Qt::DropAction defaultAction(Qt::DropActions possibleActions, - Qt::KeyboardModifiers modifiers) const Q_DECL_OVERRIDE; + Qt::KeyboardModifiers modifiers) const override; /** * to meet NSView dragImage:at guarantees, we need to record the original diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index c71e80d191..e1f36b47a1 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(); @@ -188,7 +188,7 @@ QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const if (s.length() > dragImageMaxChars) s = s.left(dragImageMaxChars -3) + QChar(0x2026); if (!s.isEmpty()) { - const int width = fm.width(s); + const int width = fm.horizontalAdvance(s); const int height = fm.height(); if (width > 0 && height > 0) { qreal dpr = 1.0; 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.h b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h index f5ba1dc22e..2ddda14289 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h @@ -59,19 +59,19 @@ public: QCocoaFileDialogHelper(); virtual ~QCocoaFileDialogHelper(); - void exec() Q_DECL_OVERRIDE; + void exec() override; - bool defaultNameFilterDisables() const Q_DECL_OVERRIDE; + bool defaultNameFilterDisables() const override; - bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) Q_DECL_OVERRIDE; - void hide() Q_DECL_OVERRIDE; - void setDirectory(const QUrl &directory) Q_DECL_OVERRIDE; - QUrl directory() const Q_DECL_OVERRIDE; - void selectFile(const QUrl &filename) Q_DECL_OVERRIDE; - QList<QUrl> selectedFiles() const Q_DECL_OVERRIDE; - void setFilter() Q_DECL_OVERRIDE; - void selectNameFilter(const QString &filter) Q_DECL_OVERRIDE; - QString selectedNameFilter() const Q_DECL_OVERRIDE; + bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; + void hide() override; + void setDirectory(const QUrl &directory) override; + QUrl directory() const override; + void selectFile(const QUrl &filename) override; + QList<QUrl> selectedFiles() const override; + void setFilter() override; + void selectNameFilter(const QString &filter) override; + QString selectedNameFilter() const override; public: bool showCocoaFilePanel(Qt::WindowModality windowModality, QWindow *parent); diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index c1c7903766..94f2125bad 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -63,7 +63,8 @@ #include <qsysinfo.h> #include <qoperatingsystemversion.h> #include <qglobal.h> -#include <QDir> +#include <qdir.h> +#include <qregularexpression.h> #include <qpa/qplatformnativeinterface.h> @@ -161,11 +162,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); // resetting our mCurrentDir, set the delegate // here to make sure it gets the correct value. [mSavePanel setDelegate:self]; - -#if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan) - mOpenPanel.accessoryViewDisclosed = YES; -#endif + mOpenPanel.accessoryViewDisclosed = YES; if (mOptions->isLabelExplicitlySet(QFileDialogOptions::Accept)) [mSavePanel setPrompt:[self strip:options->labelText(QFileDialogOptions::Accept)]]; @@ -197,7 +194,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); static QString strippedText(QString s) { - s.remove( QString::fromLatin1("...") ); + s.remove(QLatin1String("...")); return QPlatformTheme::removeMnemonics(s).trimmed(); } @@ -231,7 +228,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 +257,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 +283,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); }]; } @@ -513,9 +510,10 @@ static QString strippedText(QString s) - (QString)removeExtensions:(const QString &)filter { - QRegExp regExp(QString::fromLatin1(QPlatformFileDialogHelper::filterRegExp)); - if (regExp.indexIn(filter) != -1) - return regExp.cap(1).trimmed(); + QRegularExpression regExp(QString::fromLatin1(QPlatformFileDialogHelper::filterRegExp)); + QRegularExpressionMatch match = regExp.match(filter); + if (match.hasMatch()) + return match.captured(1).trimmed(); return filter; } diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h index c3fad7cfd6..14f12f2d76 100644 --- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h +++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h @@ -54,13 +54,13 @@ public: QCocoaFontDialogHelper(); ~QCocoaFontDialogHelper(); - void exec() Q_DECL_OVERRIDE; + void exec() override; - bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) Q_DECL_OVERRIDE; - void hide() Q_DECL_OVERRIDE; + bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; + void hide() override; - void setCurrentFont(const QFont &) Q_DECL_OVERRIDE; - QFont currentFont() const Q_DECL_OVERRIDE; + void setCurrentFont(const QFont &) override; + QFont currentFont() const override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index 1a66cec0e3..0530aa8201 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -55,22 +55,22 @@ public: QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share, const QVariant &nativeHandle); ~QCocoaGLContext(); - QSurfaceFormat format() const Q_DECL_OVERRIDE; + QSurfaceFormat format() const override; - void swapBuffers(QPlatformSurface *surface) Q_DECL_OVERRIDE; + void swapBuffers(QPlatformSurface *surface) override; - bool makeCurrent(QPlatformSurface *surface) Q_DECL_OVERRIDE; - void doneCurrent() Q_DECL_OVERRIDE; + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; - QFunctionPointer getProcAddress(const char *procName) Q_DECL_OVERRIDE; + QFunctionPointer getProcAddress(const char *procName) override; void update(); static NSOpenGLPixelFormat *createNSOpenGLPixelFormat(const QSurfaceFormat &format); NSOpenGLContext *nsOpenGLContext() const; - bool isSharing() const Q_DECL_OVERRIDE; - bool isValid() const Q_DECL_OVERRIDE; + bool isSharing() const override; + bool isValid() const override; void windowWasHidden(); @@ -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 0cf9cc0aff..84632c1487 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -55,11 +55,16 @@ #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 Q_DECLARE_LOGGING_CATEGORY(lcQpaCocoaWindow) +Q_DECLARE_LOGGING_CATEGORY(lcQpaCocoaDrawing) +Q_DECLARE_LOGGING_CATEGORY(lcQpaCocoaMouse) class QPixmap; class QString; @@ -82,13 +87,8 @@ QT_MANGLE_NAMESPACE(QNSView) *qnsview_cast(NSView *view); void qt_mac_transformProccessToForegroundApplication(); QString qt_mac_applicationName(); -int qt_mac_flipYCoordinate(int y); -qreal qt_mac_flipYCoordinate(qreal y); -QPointF qt_mac_flipPoint(const NSPoint &p); -NSPoint qt_mac_flipPoint(const QPoint &p); -NSPoint qt_mac_flipPoint(const QPointF &p); - -NSRect qt_mac_flipRect(const QRect &rect); +QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference); +QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference); Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum); @@ -191,5 +191,144 @@ QT_END_NAMESPACE QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper); +// ------------------------------------------------------------------------- + +// QAppleRefCounted expects the retain function to return the object +io_object_t q_IOObjectRetain(io_object_t obj); +// QAppleRefCounted expects the release function to return void +void q_IOObjectRelease(io_object_t obj); + +template <typename T> +class QIOType : public QAppleRefCounted<T, io_object_t, q_IOObjectRetain, q_IOObjectRelease> +{ + using QAppleRefCounted<T, io_object_t, q_IOObjectRetain, q_IOObjectRelease>::QAppleRefCounted; +}; + +// ------------------------------------------------------------------------- + +// 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 c3efe158c7..b729c7f4c0 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -55,11 +55,11 @@ #include <algorithm> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaCocoaWindow, "qt.qpa.cocoa.window"); +Q_LOGGING_CATEGORY(lcQpaCocoaDrawing, "qt.qpa.cocoa.drawing"); +Q_LOGGING_CATEGORY(lcQpaCocoaMouse, "qt.qpa.cocoa.mouse"); // // Conversion Functions @@ -234,50 +234,37 @@ QString qt_mac_applicationName() return appName; } -int qt_mac_primaryScreenHeight() -{ - QMacAutoReleasePool pool; - NSArray *screens = [NSScreen screens]; - if ([screens count] > 0) { - // The first screen in the screens array is documented to - // have the (0,0) origin and is designated the primary screen. - NSRect screenFrame = [[screens objectAtIndex: 0] frame]; - return screenFrame.size.height; - } - return 0; -} +// ------------------------------------------------------------------------- -int qt_mac_flipYCoordinate(int y) -{ - return qt_mac_primaryScreenHeight() - y; -} +/*! + \fn QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference) + \fn QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference) -qreal qt_mac_flipYCoordinate(qreal y) -{ - return qt_mac_primaryScreenHeight() - y; -} + Flips the Y coordinate of the point/rect between quadrant I and IV. -QPointF qt_mac_flipPoint(const NSPoint &p) -{ - return QPointF(p.x, qt_mac_flipYCoordinate(p.y)); -} + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. -NSPoint qt_mac_flipPoint(const QPoint &p) -{ - return NSMakePoint(p.x(), qt_mac_flipYCoordinate(p.y())); -} + By flipping the Y coordinate, we can map the point/rect between + the two coordinate systems. -NSPoint qt_mac_flipPoint(const QPointF &p) + The flip is always in relation to a reference rectangle, e.g. + the frame of the parent view, or the screen geometry. In the + latter case the specialized QCocoaScreen::mapFrom/To functions + should be used instead. +*/ +QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference) { - return NSMakePoint(p.x(), qt_mac_flipYCoordinate(p.y())); + return QPointF(pos.x(), reference.height() - pos.y()); } -NSRect qt_mac_flipRect(const QRect &rect) +QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference) { - int flippedY = qt_mac_flipYCoordinate(rect.y() + rect.height()); - return NSMakeRect(rect.x(), flippedY, rect.width(), rect.height()); + return QRectF(qt_mac_flip(rect.bottomLeft(), reference), rect.size()); } +// ------------------------------------------------------------------------- + Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum) { if (buttonNum >= 0 && buttonNum <= 31) @@ -407,6 +394,22 @@ QT_END_NAMESPACE self.panelContents.needsDisplay = YES; self.needsDisplay = YES; + [super layout]; +} + +// ------------------------------------------------------------------------- + +io_object_t q_IOObjectRetain(io_object_t obj) +{ + kern_return_t ret = IOObjectRetain(obj); + Q_ASSERT(!ret); + return obj; +} + +void q_IOObjectRelease(io_object_t obj) +{ + kern_return_t ret = IOObjectRelease(obj); + Q_ASSERT(!ret); } @end diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.h b/src/plugins/platforms/cocoa/qcocoainputcontext.h index a248ca0d8d..7190ba0de8 100644 --- a/src/plugins/platforms/cocoa/qcocoainputcontext.h +++ b/src/plugins/platforms/cocoa/qcocoainputcontext.h @@ -53,11 +53,11 @@ public: explicit QCocoaInputContext(); ~QCocoaInputContext(); - bool isValid() const Q_DECL_OVERRIDE { return true; } + bool isValid() const override { return true; } - void reset() Q_DECL_OVERRIDE; + void reset() override; - QLocale locale() const Q_DECL_OVERRIDE { return m_locale; } + QLocale locale() const override { return m_locale; } void updateLocale(); private Q_SLOTS: diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.mm b/src/plugins/platforms/cocoa/qcocoainputcontext.mm index 9221099a57..d0baea5b36 100644 --- a/src/plugins/platforms/cocoa/qcocoainputcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoainputcontext.mm @@ -129,6 +129,8 @@ void QCocoaInputContext::focusObjectChanged(QObject *focusObject) return; QCocoaWindow *window = static_cast<QCocoaWindow *>(mWindow->handle()); + if (!window) + return; QNSView *view = qnsview_cast(window->view()); if (!view) return; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index ecdd20c4dc..301771fd53 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 @@ -124,34 +75,35 @@ public: static QCocoaIntegration *instance(); Options options() const; - 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; + bool hasCapability(QPlatformIntegration::Capability cap) const override; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const override; + QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; #ifndef QT_NO_OPENGL - QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; #endif - QPlatformBackingStore *createPlatformBackingStore(QWindow *widget) const Q_DECL_OVERRIDE; + QPlatformBackingStore *createPlatformBackingStore(QWindow *widget) const override; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QAbstractEventDispatcher *createEventDispatcher() const override; - QCoreTextFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; - QCocoaNativeInterface *nativeInterface() const Q_DECL_OVERRIDE; - QPlatformInputContext *inputContext() const Q_DECL_OVERRIDE; + QCoreTextFontDatabase *fontDatabase() const override; + QCocoaNativeInterface *nativeInterface() const override; + QPlatformInputContext *inputContext() const override; #ifndef QT_NO_ACCESSIBILITY - QCocoaAccessibility *accessibility() const Q_DECL_OVERRIDE; + QCocoaAccessibility *accessibility() const override; #endif #ifndef QT_NO_CLIPBOARD - QCocoaClipboard *clipboard() const Q_DECL_OVERRIDE; + QCocoaClipboard *clipboard() const override; #endif - QCocoaDrag *drag() const Q_DECL_OVERRIDE; + QCocoaDrag *drag() const override; - QStringList themeNames() const Q_DECL_OVERRIDE; - QPlatformTheme *createPlatformTheme(const QString &name) const Q_DECL_OVERRIDE; - QCocoaServices *services() const Q_DECL_OVERRIDE; - QVariant styleHint(StyleHint hint) const Q_DECL_OVERRIDE; + QStringList themeNames() const override; + QPlatformTheme *createPlatformTheme(const QString &name) const override; + QCocoaServices *services() const override; + QVariant styleHint(StyleHint hint) const override; - Qt::KeyboardModifiers queryKeyboardModifiers() const Q_DECL_OVERRIDE; - QList<int> possibleKeys(const QKeyEvent *event) const Q_DECL_OVERRIDE; + Qt::KeyboardModifiers queryKeyboardModifiers() const override; + QList<int> possibleKeys(const QKeyEvent *event) const override; void updateScreens(); QCocoaScreen *screenForNSScreen(NSScreen *nsScreen); @@ -165,9 +117,12 @@ public: QCocoaWindow *activePopupWindow() const; QList<QCocoaWindow *> *popupWindowStack(); - void setApplicationIcon(const QIcon &icon) const Q_DECL_OVERRIDE; + void setApplicationIcon(const QIcon &icon) const override; + + void beep() const override; - void beep() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void focusWindowChanged(QWindow *); private: static QCocoaIntegration *mInstance; 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..2b4cf0ef98 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.h +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -45,6 +45,8 @@ #include <qpa/qplatformmenu.h> #include "qcocoamenuitem.h" +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaNSMenu)); + QT_BEGIN_NAMESPACE class QCocoaMenuBar; @@ -55,37 +57,31 @@ 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; - void setEnabled(bool enabled) Q_DECL_OVERRIDE; - bool isEnabled() const Q_DECL_OVERRIDE; - void setVisible(bool visible) Q_DECL_OVERRIDE; - void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) Q_DECL_OVERRIDE; - void dismiss() Q_DECL_OVERRIDE; + void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; + void removeMenuItem(QPlatformMenuItem *menuItem) override; + void syncMenuItem(QPlatformMenuItem *menuItem) override; + void setEnabled(bool enabled) override; + bool isEnabled() const override; + void setVisible(bool visible) override; + void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) override; + void dismiss() override; - void syncSeparatorsCollapsible(bool enable) Q_DECL_OVERRIDE; + void syncSeparatorsCollapsible(bool enable) override; void propagateEnabledState(bool enabled); - void setIcon(const QIcon &icon) Q_DECL_OVERRIDE { Q_UNUSED(icon) } + void setIcon(const QIcon &icon) override { Q_UNUSED(icon) } - void setText(const QString &text) Q_DECL_OVERRIDE; - void setMinimumWidth(int width) Q_DECL_OVERRIDE; - void setFont(const QFont &font) Q_DECL_OVERRIDE; + void setText(const QString &text) override; + void setMinimumWidth(int width) override; + void setFont(const QFont &font) override; - inline NSMenu *nsMenu() const - { return m_nativeMenu; } + NSMenu *nsMenu() const; inline bool isVisible() const { return m_visible; } - QPlatformMenuItem *menuItemAt(int position) const Q_DECL_OVERRIDE; - QPlatformMenuItem *menuItemForTag(quintptr tag) const Q_DECL_OVERRIDE; + QPlatformMenuItem *menuItemAt(int position) const override; + QPlatformMenuItem *menuItemForTag(quintptr tag) const override; QList<QCocoaMenuItem *> items() const; QList<QCocoaMenuItem *> merged() const; @@ -96,7 +92,7 @@ public: bool isOpen() const; void setIsOpen(bool isOpen); - void timerEvent(QTimerEvent *e) Q_DECL_OVERRIDE; + void timerEvent(QTimerEvent *e) override; void syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUpdate); @@ -106,9 +102,8 @@ private: void scheduleUpdate(); QList<QCocoaMenuItem *> m_menuItems; - NSMenu *m_nativeMenu; + QT_MANGLE_NAMESPACE(QCocoaNSMenu) *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 e41c70b8ca..b3c2d5ae90 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -38,229 +38,22 @@ ****************************************************************************/ #include "qcocoamenu.h" +#include "qcocoansmenu.h" #include "qcocoahelpers.h" #include <QtCore/QtDebug> -#include <QtCore/qmetaobject.h> -#include <QtCore/private/qthread_p.h> -#include <QtGui/private/qguiapplication_p.h> #include "qcocoaapplication.h" #include "qcocoaintegration.h" #include "qcocoamenuloader.h" #include "qcocoamenubar.h" #include "qcocoawindow.h" -#import "qnsview.h" - -QT_BEGIN_NAMESPACE - -NSString *qt_mac_removePrivateUnicode(NSString* string) -{ - int len = [string length]; - if (len) { - QVarLengthArray <unichar, 10> characters(len); - bool changed = false; - for (int i = 0; i<len; i++) { - characters[i] = [string characterAtIndex:i]; - // check if they belong to key codes in private unicode range - // currently we need to handle only the NSDeleteFunctionKey - if (characters[i] == NSDeleteFunctionKey) { - characters[i] = NSDeleteCharacter; - changed = true; - } - } - if (changed) - return [NSString stringWithCharacters:characters.data() length:len]; - } - return string; -} - -QT_END_NAMESPACE - -@interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> { - QCocoaMenu *m_menu; -} - -- (id) initWithMenu:(QCocoaMenu*) m; -- (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaMenuDelegate); - -@implementation QCocoaMenuDelegate - -- (id) initWithMenu:(QCocoaMenu*) m -{ - if ((self = [super init])) - m_menu = m; - - return self; -} - -- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu -{ - Q_ASSERT(m_menu->nsMenu() == menu); - return menu.numberOfItems; -} - -- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel -{ - Q_UNUSED(index); - Q_ASSERT(m_menu->nsMenu() == menu); - if (shouldCancel) { - // TODO detach all submenus - return NO; - } - - QCocoaMenuItem *menuItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); - if (m_menu->items().contains(menuItem)) { - if (QCocoaMenu *itemSubmenu = menuItem->menu()) - itemSubmenu->setAttachedItem(item); - } - return YES; -} - -- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item -{ - Q_UNUSED(menu); - if (item && [item tag]) { - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); - cocoaItem->hovered(); - } -} - -- (void) menuWillOpen:(NSMenu*)m -{ - Q_UNUSED(m); - m_menu->setIsOpen(true); - emit m_menu->aboutToShow(); -} - -- (void) menuDidClose:(NSMenu*)m -{ - Q_UNUSED(m); - m_menu->setIsOpen(false); - // wrong, but it's the best we can do - emit m_menu->aboutToHide(); -} - -- (void) itemFired:(NSMenuItem*) item -{ - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); - // Menu-holding items also get a target to play nicely - // with NSMenuValidation but should not trigger. - if (cocoaItem->menu()) - return; - QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); - QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; - static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); - activatedSignal.invoke(cocoaItem, Qt::QueuedConnection); -} - -- (BOOL)validateMenuItem:(NSMenuItem*)menuItem -{ - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>(menuItem.tag); - // Menu-holding items are always enabled, as it's conventional in Cocoa - if (!cocoaItem || cocoaItem->menu()) - return YES; - - return cocoaItem->isEnabled(); -} - -- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action -{ - /* - Check if the menu actually has a keysequence defined for this key event. - If it does, then we will first send the key sequence to the QWidget that has focus - since (in Qt's eyes) it needs to a chance at the key event first (QEvent::ShortcutOverride). - If the widget accepts the key event, we then return YES, but set the target and action to be nil, - which means that the action should not be triggered, and instead dispatch the event ourselves. - In every other case we return NO, which means that Cocoa can do as it pleases - (i.e., fire the menu action). - */ - - // Change the private unicode keys to the ones used in setting the "Key Equivalents" - NSString *characters = qt_mac_removePrivateUnicode([event characters]); - // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... - const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; - if (NSMenuItem *menuItem = [self findItem:menu forKey:characters forModifiers:([event modifierFlags] & mask)]) { - if (!menuItem.target) { - // This item was modified by QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder - // and it looks like we're running a modal session for NSOpenPanel/NSSavePanel. - // QCocoaFileDialogHelper is actually the only place we use this and we run NSOpenPanel modal - // (modal sheet, window modal, application modal). - // Whatever the current first responder is, let's give it a chance - // and do not touch the Qt's focusObject (which is different from some native view - // having a focus inside NSSave/OpenPanel. - *target = nil; - *action = menuItem.action; - return YES; - } - - QObject *object = qApp->focusObject(); - if (object) { - QChar ch; - int keyCode; - ulong nativeModifiers = [event modifierFlags]; - Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; - NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; - NSString *characters = [event characters]; - - if ([charactersIgnoringModifiers length] > 0) { // convert the first character into a key code - if ((modifiers & Qt::ControlModifier) && ([characters length] != 0)) { - ch = QChar([characters characterAtIndex:0]); - } else { - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - } - keyCode = qt_mac_cocoaKey2QtKey(ch); - } else { - // might be a dead key - ch = QChar::ReplacementCharacter; - keyCode = Qt::Key_unknown; - } - - QKeyEvent accel_ev(QEvent::ShortcutOverride, (keyCode & (~Qt::KeyboardModifierMask)), - Qt::KeyboardModifiers(modifiers & Qt::KeyboardModifierMask)); - accel_ev.ignore(); - QCoreApplication::sendEvent(object, &accel_ev); - if (accel_ev.isAccepted()) { - [[NSApp keyWindow] sendEvent: event]; - *target = nil; - *action = nil; - return YES; - } - } - } - return NO; -} - -- (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier -{ - for (NSMenuItem *item in [menu itemArray]) { - if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) - continue; - if ([item hasSubmenu]) { - if (NSMenuItem *nested = [self findItem:[item submenu] forKey:key forModifiers:modifier]) - return nested; - } - - NSString *menuKey = [item keyEquivalent]; - if (menuKey - && NSOrderedSame == [menuKey compare:key] - && modifier == [item keyEquivalentModifierMask]) - return item; - } - return nil; -} - -@end +#include "qcocoascreen.h" QT_BEGIN_NAMESPACE QCocoaMenu::QCocoaMenu() : m_attachedItem(0), - m_tag(0), m_updateTimer(0), m_enabled(true), m_parentEnabled(true), @@ -269,9 +62,7 @@ QCocoaMenu::QCocoaMenu() : { QMacAutoReleasePool pool; - m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"]; - [m_nativeMenu setAutoenablesItems:YES]; - m_nativeMenu.delegate = [[QCocoaMenuDelegate alloc] initWithMenu:this]; + m_nativeMenu = [[QCocoaNSMenu alloc] initWithQPAMenu:this]; } QCocoaMenu::~QCocoaMenu() @@ -281,10 +72,6 @@ QCocoaMenu::~QCocoaMenu() item->setMenuParent(0); } - QMacAutoReleasePool pool; - NSObject *delegate = m_nativeMenu.delegate; - m_nativeMenu.delegate = nil; - [delegate release]; [m_nativeMenu release]; } @@ -309,6 +96,11 @@ void QCocoaMenu::setFont(const QFont &font) } } +NSMenu *QCocoaMenu::nsMenu() const +{ + return static_cast<NSMenu *>(m_nativeMenu); +} + void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) { QMacAutoReleasePool pool; diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h index a4ee531e91..6f3aca3a51 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.h +++ b/src/plugins/platforms/cocoa/qcocoamenubar.h @@ -56,11 +56,11 @@ public: QCocoaMenuBar(); ~QCocoaMenuBar(); - void insertMenu(QPlatformMenu *menu, QPlatformMenu* before) Q_DECL_OVERRIDE; - void removeMenu(QPlatformMenu *menu) Q_DECL_OVERRIDE; - void syncMenu(QPlatformMenu *menuItem) Q_DECL_OVERRIDE; - void handleReparent(QWindow *newParentWindow) Q_DECL_OVERRIDE; - QPlatformMenu *menuForTag(quintptr tag) const Q_DECL_OVERRIDE; + void insertMenu(QPlatformMenu *menu, QPlatformMenu* before) override; + void removeMenu(QPlatformMenu *menu) override; + void syncMenu(QPlatformMenu *menuItem) override; + void handleReparent(QWindow *newParentWindow) override; + QPlatformMenu *menuForTag(quintptr tag) const override; inline NSMenu *nsMenu() const { return m_nativeMenu; } @@ -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..8091d00bda 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.mm +++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm @@ -449,7 +449,12 @@ NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole r) foreach (QCocoaMenuItem *i, m->items()) if (i->effectiveRole() == r) return i->nsItem(); - return Q_NULLPTR; + return 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..2b598ee3a0 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -78,27 +78,22 @@ 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; - void setVisible(bool isVisible) Q_DECL_OVERRIDE; - void setIsSeparator(bool isSeparator) Q_DECL_OVERRIDE; - void setFont(const QFont &font) Q_DECL_OVERRIDE; - void setRole(MenuRole role) Q_DECL_OVERRIDE; + 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 &font) override; + void setRole(MenuRole role) override; #ifndef QT_NO_SHORTCUT - void setShortcut(const QKeySequence& shortcut) Q_DECL_OVERRIDE; + void setShortcut(const QKeySequence& shortcut) override; #endif - void setCheckable(bool checkable) Q_DECL_OVERRIDE { Q_UNUSED(checkable) } - void setChecked(bool isChecked) Q_DECL_OVERRIDE; - void setEnabled(bool isEnabled) Q_DECL_OVERRIDE; - void setIconSize(int size) Q_DECL_OVERRIDE; + void setCheckable(bool checkable) override { Q_UNUSED(checkable) } + void setChecked(bool isChecked) override; + void setEnabled(bool isEnabled) override; + void setIconSize(int size) override; - void setNativeContents(WId item) Q_DECL_OVERRIDE; + void setNativeContents(WId item) override; inline QString text() const { return m_text; } inline NSMenuItem * nsItem() { return m_native; } @@ -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..f8f9648822 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -49,6 +49,7 @@ #include "qcocoaapplication.h" // for custom application category #include "qcocoamenuloader.h" #include <QtGui/private/qcoregraphics_p.h> +#include <QtCore/qregularexpression.h> #include <QtCore/QDebug> @@ -95,7 +96,6 @@ QCocoaMenuItem::QCocoaMenuItem() : m_itemView(nil), m_menu(NULL), m_role(NoRole), - m_tag(0), m_iconSize(16), m_textSynced(false), m_isVisible(true), @@ -262,7 +262,8 @@ NSMenuItem *QCocoaMenuItem::sync() m_detectedRole = detectMenuRole(m_text); switch (m_detectedRole) { case QPlatformMenuItem::AboutRole: - if (m_text.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) + if (m_text.indexOf(QRegularExpression(QString::fromLatin1("qt$"), + QRegularExpression::CaseInsensitiveOption)) == -1) mergeItem = [loader aboutMenuItem]; else mergeItem = [loader aboutQtMenuItem]; diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm index b986833f6d..cd597da71c 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm @@ -57,8 +57,12 @@ static QCocoaMenuLoader *shared = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - shared = [[self alloc] init]; - }); + shared = [[self alloc] init]; + atexit_b(^{ + [shared release]; + shared = nil; + }); + }); return shared; } 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.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h index 26fbe3e4bc..c78f1d5a7f 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h @@ -60,18 +60,18 @@ public: QCocoaNativeInterface(); #ifndef QT_NO_OPENGL - void *nativeResourceForContext(const QByteArray &resourceString, QOpenGLContext *context) Q_DECL_OVERRIDE; + void *nativeResourceForContext(const QByteArray &resourceString, QOpenGLContext *context) override; #endif - void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) Q_DECL_OVERRIDE; + void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) override; - NativeResourceForIntegrationFunction nativeResourceFunctionForIntegration(const QByteArray &resource) Q_DECL_OVERRIDE; + NativeResourceForIntegrationFunction nativeResourceFunctionForIntegration(const QByteArray &resource) override; #ifndef QT_NO_OPENGL static void *cglContextForContext(QOpenGLContext *context); static void *nsOpenGLContextForContext(QOpenGLContext* context); #endif - QFunctionPointer platformFunction(const QByteArray &function) const Q_DECL_OVERRIDE; + QFunctionPointer platformFunction(const QByteArray &function) const override; public Q_SLOTS: void onAppFocusWindowChanged(QWindow *window); diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 5504c2427f..955b147bfd 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; } @@ -233,7 +233,7 @@ QFunctionPointer QCocoaNativeInterface::platformFunction(const QByteArray &funct if (function == QCocoaWindowFunctions::bottomLeftClippedByNSWindowOffsetIdentifier()) return QFunctionPointer(QCocoaWindowFunctions::BottomLeftClippedByNSWindowOffset(QCocoaWindow::bottomLeftClippedByNSWindowOffsetStatic)); - return Q_NULLPTR; + return nullptr; } void QCocoaNativeInterface::addToMimeList(void *macPasteboardMime) diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.h b/src/plugins/platforms/cocoa/qcocoansmenu.h new file mode 100644 index 0000000000..8fb0a26f27 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoansmenu.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QCOCOANSMENU_H +#define QCOCOANSMENU_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#import <AppKit/AppKit.h> + +#include <QtCore/qpointer.h> +#include <qcocoahelpers.h> + +QT_FORWARD_DECLARE_CLASS(QCocoaMenu); +typedef QPointer<QCocoaMenu> QCocoaMenuPointer; + + +@interface QT_MANGLE_NAMESPACE(QCocoaNSMenuDelegate) : NSObject <NSMenuDelegate> + ++ (instancetype)sharedMenuDelegate; + +- (NSMenuItem *)findItemInMenu:(NSMenu *)menu + forKey:(NSString *)key + modifiers:(NSUInteger)modifiers; + +- (BOOL)validateMenuItem:(NSMenuItem *)item; // NSMenuValidation + +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaNSMenu) : NSMenu + +@property (readonly, nonatomic) QCocoaMenuPointer qpaMenu; + +- (instancetype)initWithQPAMenu:(QCocoaMenu *)menu; + +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenu); +QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenuDelegate); + +#endif // QCOCOANSMENU_H diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm new file mode 100644 index 0000000000..996a4ff194 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoansmenu.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$ +** +****************************************************************************/ + +#import "qcocoansmenu.h" +#include "qcocoamenu.h" +#include "qcocoamenuitem.h" +#import "qnsview.h" + +#include <QtCore/qmetaobject.h> +#include <QtCore/private/qthread_p.h> +#include <QtGui/private/qguiapplication_p.h> + +static NSString *qt_mac_removePrivateUnicode(NSString* string) +{ + int len = [string length]; + if (len) { + QVarLengthArray <unichar, 10> characters(len); + bool changed = false; + for (int i = 0; i<len; i++) { + characters[i] = [string characterAtIndex:i]; + // check if they belong to key codes in private unicode range + // currently we need to handle only the NSDeleteFunctionKey + if (characters[i] == NSDeleteFunctionKey) { + characters[i] = NSDeleteCharacter; + changed = true; + } + } + if (changed) + return [NSString stringWithCharacters:characters.data() length:len]; + } + return string; +} + +@implementation QCocoaNSMenu + +- (instancetype)initWithQPAMenu:(QCocoaMenu *)menu +{ + if ((self = [super initWithTitle:@"Untitled"])) { + _qpaMenu = menu; + self.autoenablesItems = YES; + self.delegate = [QCocoaNSMenuDelegate sharedMenuDelegate]; + } + + return self; +} + +@end + +#define CHECK_MENU_CLASS(menu) Q_ASSERT([menu isMemberOfClass:[QCocoaNSMenu class]]) + +@implementation QCocoaNSMenuDelegate + ++ (instancetype)sharedMenuDelegate +{ + static QCocoaNSMenuDelegate *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[self alloc] init]; + atexit_b(^{ + [shared release]; + shared = nil; + }); + }); + return shared; +} + +- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu +{ + CHECK_MENU_CLASS(menu); + return menu.numberOfItems; +} + +- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel +{ + Q_UNUSED(index); + CHECK_MENU_CLASS(menu); + + if (shouldCancel) + return NO; + + const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; + if (qpaMenu.isNull()) + return YES; + + auto *menuItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + if (qpaMenu->items().contains(menuItem)) { + if (QCocoaMenu *itemSubmenu = menuItem->menu()) + itemSubmenu->setAttachedItem(item); + } + + return YES; +} + +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item +{ + CHECK_MENU_CLASS(menu); + auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + if (qpaItem) + qpaItem->hovered(); +} + +- (void)menuWillOpen:(NSMenu *)menu +{ + CHECK_MENU_CLASS(menu); + const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; + if (qpaMenu.isNull()) + return; + + qpaMenu->setIsOpen(true); + emit qpaMenu->aboutToShow(); +} + +- (void)menuDidClose:(NSMenu *)menu +{ + CHECK_MENU_CLASS(menu); + const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; + if (qpaMenu.isNull()) + return; + + qpaMenu->setIsOpen(false); + // wrong, but it's the best we can do + emit qpaMenu->aboutToHide(); +} + +- (void)itemFired:(NSMenuItem *)item +{ + auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + // Menu-holding items also get a target to play nicely + // with NSMenuValidation but should not trigger. + if (!qpaItem || qpaItem->menu()) + return; + + QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); + QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; + + static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); + activatedSignal.invoke(qpaItem, Qt::QueuedConnection); +} + +- (BOOL)validateMenuItem:(NSMenuItem*)item +{ + auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + // Menu-holding items are always enabled, as it's conventional in Cocoa + if (!qpaItem || qpaItem->menu()) + return YES; + + return qpaItem->isEnabled(); +} + +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action +{ + /* + Check if the menu actually has a keysequence defined for this key event. + If it does, then we will first send the key sequence to the QWidget that has focus + since (in Qt's eyes) it needs to a chance at the key event first (QEvent::ShortcutOverride). + If the widget accepts the key event, we then return YES, but set the target and action to be nil, + which means that the action should not be triggered, and instead dispatch the event ourselves. + In every other case we return NO, which means that Cocoa can do as it pleases + (i.e., fire the menu action). + */ + + CHECK_MENU_CLASS(menu); + + // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... + static const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; + + // Change the private unicode keys to the ones used in setting the "Key Equivalents" + NSString *characters = qt_mac_removePrivateUnicode(event.charactersIgnoringModifiers); + const auto modifiers = event.modifierFlags & mask; + NSMenuItem *keyEquivalentItem = [self findItemInMenu:menu + forKey:characters + modifiers:modifiers]; + if (!keyEquivalentItem) { + // Maybe the modified character is what we're looking for after all + characters = qt_mac_removePrivateUnicode(event.characters); + keyEquivalentItem = [self findItemInMenu:menu + forKey:characters + modifiers:modifiers]; + } + + if (keyEquivalentItem) { + if (!keyEquivalentItem.target) { + // This item was modified by QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder + // and it looks like we're running a modal session for NSOpenPanel/NSSavePanel. + // QCocoaFileDialogHelper is actually the only place we use this and we run NSOpenPanel modal + // (modal sheet, window modal, application modal). + // Whatever the current first responder is, let's give it a chance + // and do not touch the Qt's focusObject (which is different from some native view + // having a focus inside NSSave/OpenPanel. + *target = nil; + *action = keyEquivalentItem.action; + return YES; + } + + QObject *object = qApp->focusObject(); + if (object) { + QChar ch; + int keyCode; + ulong nativeModifiers = [event modifierFlags]; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; + NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; + NSString *characters = [event characters]; + + if ([charactersIgnoringModifiers length] > 0) { // convert the first character into a key code + if ((modifiers & Qt::ControlModifier) && ([characters length] != 0)) { + ch = QChar([characters characterAtIndex:0]); + } else { + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + } + keyCode = qt_mac_cocoaKey2QtKey(ch); + } else { + // might be a dead key + ch = QChar::ReplacementCharacter; + keyCode = Qt::Key_unknown; + } + + QKeyEvent accel_ev(QEvent::ShortcutOverride, (keyCode & (~Qt::KeyboardModifierMask)), + Qt::KeyboardModifiers(modifiers & Qt::KeyboardModifierMask)); + accel_ev.ignore(); + QCoreApplication::sendEvent(object, &accel_ev); + if (accel_ev.isAccepted()) { + [[NSApp keyWindow] sendEvent: event]; + *target = nil; + *action = nil; + return YES; + } + } + } + + return NO; +} + +- (NSMenuItem *)findItemInMenu:(NSMenu *)menu + forKey:(NSString *)key + modifiers:(NSUInteger)modifiers +{ + // Find an item in 'menu' that has the same key equivalent as specified by + // 'key' and 'modifiers'. We ignore disabled, hidden and separator items. + // In a similar fashion, we don't need to recurse into submenus because their + // delegate will have [menuHasKeyEquivalent:...] invoked at some point. + + for (NSMenuItem *item in menu.itemArray) { + if (!item.enabled || item.hidden || item.separatorItem) + continue; + + if (item.hasSubmenu) + continue; + + NSString *menuKey = item.keyEquivalent; + if (menuKey && NSOrderedSame == [menuKey compare:key] + && modifiers == item.keyEquivalentModifierMask) + return item; + } + + return nil; +} + +@end + +#undef CHECK_MENU_CLASS diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.h b/src/plugins/platforms/cocoa/qcocoaprintdevice.h index c726cca4e5..20b27f3286 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.h +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.h @@ -68,37 +68,37 @@ public: explicit QCocoaPrintDevice(const QString &id); virtual ~QCocoaPrintDevice(); - bool isValid() const Q_DECL_OVERRIDE; - bool isDefault() const Q_DECL_OVERRIDE; + bool isValid() const override; + bool isDefault() const override; - QPrint::DeviceState state() const Q_DECL_OVERRIDE; + QPrint::DeviceState state() const override; - QPageSize defaultPageSize() const Q_DECL_OVERRIDE; + QPageSize defaultPageSize() const override; QMarginsF printableMargins(const QPageSize &pageSize, QPageLayout::Orientation orientation, - int resolution) const Q_DECL_OVERRIDE; + int resolution) const override; - int defaultResolution() const Q_DECL_OVERRIDE; + int defaultResolution() const override; - QPrint::InputSlot defaultInputSlot() const Q_DECL_OVERRIDE; + QPrint::InputSlot defaultInputSlot() const override; - QPrint::OutputBin defaultOutputBin() const Q_DECL_OVERRIDE; + QPrint::OutputBin defaultOutputBin() const override; - QPrint::DuplexMode defaultDuplexMode() const Q_DECL_OVERRIDE; + QPrint::DuplexMode defaultDuplexMode() const override; - QPrint::ColorMode defaultColorMode() const Q_DECL_OVERRIDE; + QPrint::ColorMode defaultColorMode() const override; PMPrinter macPrinter() const; PMPaper macPaper(const QPageSize &pageSize) const; protected: - void loadPageSizes() const Q_DECL_OVERRIDE; - void loadResolutions() const Q_DECL_OVERRIDE; - void loadInputSlots() const Q_DECL_OVERRIDE; - void loadOutputBins() const Q_DECL_OVERRIDE; - void loadDuplexModes() const Q_DECL_OVERRIDE; - void loadColorModes() const Q_DECL_OVERRIDE; - void loadMimeTypes() const Q_DECL_OVERRIDE; + void loadPageSizes() const override; + void loadResolutions() const override; + void loadInputSlots() const override; + void loadOutputBins() const override; + void loadDuplexModes() const override; + void loadColorModes() const override; + void loadMimeTypes() const override; private: QPageSize createPageSize(const PMPaper &paper) const; diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm index 39fcf285ed..bfe6cd09b6 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm @@ -46,6 +46,15 @@ QT_BEGIN_NAMESPACE #ifndef QT_NO_PRINTER +// The CUPS PPD APIs were deprecated in CUPS 1.6/macOS 10.8, but +// as long as we're supporting RHEL 6, which still ships CUPS 1.4 +// we're not going to rewrite this, as we want to share the code +// between macOS and Linux for the CUPS-bits. See discussion in +// https://bugreports.qt.io/browse/QTBUG-56545 +#pragma message "Disabling CUPS PPD deprecation warnings. This should be fixed once we drop support for RHEL6 (QTBUG-56545)" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + static QPrint::DuplexMode macToDuplexMode(const PMDuplexMode &mode) { if (mode == kPMDuplexTumble) @@ -474,6 +483,8 @@ PMPaper QCocoaPrintDevice::macPaper(const QPageSize &pageSize) const return paper; } +#pragma clang diagnostic pop + #endif // QT_NO_PRINTER QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaprintersupport.h b/src/plugins/platforms/cocoa/qcocoaprintersupport.h index a07bf0ec1b..40a638207a 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintersupport.h +++ b/src/plugins/platforms/cocoa/qcocoaprintersupport.h @@ -53,12 +53,12 @@ public: QCocoaPrinterSupport(); ~QCocoaPrinterSupport(); - QPrintEngine *createNativePrintEngine(QPrinter::PrinterMode printerMode, const QString &deviceId = QString()) Q_DECL_OVERRIDE; - QPaintEngine *createPaintEngine(QPrintEngine *, QPrinter::PrinterMode printerMode) Q_DECL_OVERRIDE; + QPrintEngine *createNativePrintEngine(QPrinter::PrinterMode printerMode, const QString &deviceId = QString()) override; + QPaintEngine *createPaintEngine(QPrintEngine *, QPrinter::PrinterMode printerMode) override; - QPrintDevice createPrintDevice(const QString &id) Q_DECL_OVERRIDE; - QStringList availablePrintDeviceIds() const Q_DECL_OVERRIDE; - QString defaultPrintDeviceId() const Q_DECL_OVERRIDE; + QPrintDevice createPrintDevice(const QString &id) override; + QStringList availablePrintDeviceIds() const override; + QString defaultPrintDeviceId() const override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h new file mode 100644 index 0000000000..3d59c3de79 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoascreen.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 override; + QRect geometry() const override { return m_geometry; } + QRect availableGeometry() const override { return m_availableGeometry; } + int depth() const override { return m_depth; } + QImage::Format format() const override { return m_format; } + qreal devicePixelRatio() const override; + QSizeF physicalSize() const override { return m_physicalSize; } + QDpi logicalDpi() const override { return m_logicalDpi; } + qreal refreshRate() const override { return m_refreshRate; } + QString name() const override { return m_name; } + QPlatformCursor *cursor() const override { return m_cursor; } + QWindow *topLevelAt(const QPoint &point) const override; + QList<QPlatformScreen *> virtualSiblings() const override { return m_siblings; } + QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override; + + // ---------------------------------------------------- + // Additional methods + void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } + NSScreen *nativeScreen() const; + void updateGeometry(); + + static QCocoaScreen *primaryScreen(); + + static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); + static CGRect mapToNative(const QRectF &rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); + static QPointF mapFromNative(CGPoint pos, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); + static QRectF mapFromNative(CGRect rect, QCocoaScreen *screen = QCocoaScreen::primaryScreen()); + +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..ed1b19cd53 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -0,0 +1,309 @@ +/**************************************************************************** +** +** 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]; +} + +static QString displayName(CGDirectDisplayID displayID) +{ + QIOType<io_iterator_t> iterator; + if (IOServiceGetMatchingServices(kIOMasterPortDefault, + IOServiceMatching("IODisplayConnect"), &iterator)) + return QString(); + + QIOType<io_service_t> display; + while ((display = IOIteratorNext(iterator)) != 0) + { + NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary( + display, kIODisplayOnlyPreferredName) autorelease]; + + if ([[info objectForKey:@kDisplayVendorID] longValue] != CGDisplayVendorNumber(displayID)) + continue; + + if ([[info objectForKey:@kDisplayProductID] longValue] != CGDisplayModelNumber(displayID)) + continue; + + if ([[info objectForKey:@kDisplaySerialNumber] longValue] != CGDisplaySerialNumber(displayID)) + continue; + + NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName]; + if (![localizedNames count]) + break; // Correct screen, but no name in dictionary + + return QString::fromNSString([localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]); + } + + return QString(); +} + +void QCocoaScreen::updateGeometry() +{ + NSScreen *nsScreen = nativeScreen(); + if (!nsScreen) + return; + + // The reference screen for the geometry is always the primary screen + QRectF primaryScreenGeometry = QRectF::fromCGRect([[NSScreen screens] firstObject].frame); + m_geometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect(); + m_availableGeometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.visibleFrame), primaryScreenGeometry).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; + + m_name = displayName(dpy); + + 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 = mapToNative(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()); +} + +CGPoint QCocoaScreen::mapToNative(const QPointF &pos, QCocoaScreen *screen) +{ + Q_ASSERT(screen); + return qt_mac_flip(pos, screen->geometry()).toCGPoint(); +} + +CGRect QCocoaScreen::mapToNative(const QRectF &rect, QCocoaScreen *screen) +{ + Q_ASSERT(screen); + return qt_mac_flip(rect, screen->geometry()).toCGRect(); +} + +QPointF QCocoaScreen::mapFromNative(CGPoint pos, QCocoaScreen *screen) +{ + Q_ASSERT(screen); + return qt_mac_flip(QPointF::fromCGPoint(pos), screen->geometry()); +} + +QRectF QCocoaScreen::mapFromNative(CGRect rect, QCocoaScreen *screen) +{ + Q_ASSERT(screen); + return qt_mac_flip(QRectF::fromCGRect(rect), screen->geometry()); +} + +#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/qcocoaservices.h b/src/plugins/platforms/cocoa/qcocoaservices.h index c2ceede0f0..62a8891f96 100644 --- a/src/plugins/platforms/cocoa/qcocoaservices.h +++ b/src/plugins/platforms/cocoa/qcocoaservices.h @@ -47,8 +47,8 @@ QT_BEGIN_NAMESPACE class QCocoaServices : public QPlatformServices { public: - bool openUrl(const QUrl &url) Q_DECL_OVERRIDE; - bool openDocument(const QUrl &url) Q_DECL_OVERRIDE; + bool openUrl(const QUrl &url) override; + bool openDocument(const QUrl &url) override; }; 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/qcocoasystemtrayicon.h b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h index d542a3cb8f..2f1a1e42a9 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h @@ -57,17 +57,17 @@ class Q_GUI_EXPORT QCocoaSystemTrayIcon : public QPlatformSystemTrayIcon public: QCocoaSystemTrayIcon() : m_sys(0) {} - void init() Q_DECL_OVERRIDE; - void cleanup() Q_DECL_OVERRIDE; - void updateIcon(const QIcon &icon) Q_DECL_OVERRIDE; - void updateToolTip(const QString &toolTip) Q_DECL_OVERRIDE; - void updateMenu(QPlatformMenu *menu) Q_DECL_OVERRIDE; - QRect geometry() const Q_DECL_OVERRIDE; + void init() override; + void cleanup() override; + void updateIcon(const QIcon &icon) override; + void updateToolTip(const QString &toolTip) override; + void updateMenu(QPlatformMenu *menu) override; + QRect geometry() const override; void showMessage(const QString &title, const QString &msg, - const QIcon& icon, MessageIcon iconType, int secs) Q_DECL_OVERRIDE; + const QIcon& icon, MessageIcon iconType, int secs) override; - bool isSystemTrayAvailable() const Q_DECL_OVERRIDE; - bool supportsMessages() const Q_DECL_OVERRIDE; + bool isSystemTrayAvailable() const override; + bool supportsMessages() const override; private: QSystemTrayIconSys *m_sys; diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h index 27c071a8cd..69eaf8db56 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.h +++ b/src/plugins/platforms/cocoa/qcocoatheme.h @@ -56,25 +56,25 @@ public: void reset(); - QPlatformMenuItem* createPlatformMenuItem() const Q_DECL_OVERRIDE; - QPlatformMenu* createPlatformMenu() const Q_DECL_OVERRIDE; - QPlatformMenuBar* createPlatformMenuBar() const Q_DECL_OVERRIDE; + QPlatformMenuItem* createPlatformMenuItem() const override; + QPlatformMenu* createPlatformMenu() const override; + QPlatformMenuBar* createPlatformMenuBar() const override; #ifndef QT_NO_SYSTEMTRAYICON - QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const Q_DECL_OVERRIDE; + QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; #endif - bool usePlatformNativeDialog(DialogType dialogType) const Q_DECL_OVERRIDE; - QPlatformDialogHelper *createPlatformDialogHelper(DialogType dialogType) const Q_DECL_OVERRIDE; + bool usePlatformNativeDialog(DialogType dialogType) const override; + QPlatformDialogHelper *createPlatformDialogHelper(DialogType dialogType) const override; - const QPalette *palette(Palette type = SystemPalette) const Q_DECL_OVERRIDE; - const QFont *font(Font type = SystemFont) const Q_DECL_OVERRIDE; - QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const Q_DECL_OVERRIDE; + const QPalette *palette(Palette type = SystemPalette) const override; + const QFont *font(Font type = SystemFont) const override; + QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = 0) const override; - QVariant themeHint(ThemeHint hint) const Q_DECL_OVERRIDE; - QString standardButtonText(int button) const Q_DECL_OVERRIDE; - QKeySequence standardButtonShortcut(int button) const Q_DECL_OVERRIDE; + QVariant themeHint(ThemeHint hint) const override; + QString standardButtonText(int button) const override; + QKeySequence standardButtonShortcut(int button) const override; static const char *name; diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index a1c1b379f7..93f0400916 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> @@ -97,7 +98,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaThemeNotificationReceiver); { Q_UNUSED(notification); mPrivate->reset(); - QWindowSystemInterface::handleThemeChange(Q_NULLPTR); + QWindowSystemInterface::handleThemeChange(nullptr); } @end @@ -126,7 +127,7 @@ QCocoaTheme::~QCocoaTheme() void QCocoaTheme::reset() { delete m_systemPalette; - m_systemPalette = Q_NULLPTR; + m_systemPalette = nullptr; qDeleteAll(m_palettes); m_palettes.clear(); } @@ -274,7 +275,7 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const } if (iconType != 0) { QPixmap pixmap; - IconRef icon = Q_NULLPTR; + IconRef icon = nullptr; GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon); if (icon) { @@ -341,9 +342,13 @@ 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)); + case QPlatformTheme::UseFullScreenForPopupMenu: + return QVariant(bool([[NSApplication sharedApplication] presentationOptions] & NSApplicationPresentationFullScreen)); default: break; } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index aa8fffdf7e..fb91c53a7a 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -50,82 +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); - -// @compatibility_alias doesn't work with protocols -#define QNSWindowProtocol QT_MANGLE_NAMESPACE(QNSWindowProtocol) - -@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 @@ -162,50 +95,51 @@ public: QCocoaWindow(QWindow *tlw, WId nativeHandle = 0); ~QCocoaWindow(); - void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; - QRect geometry() const Q_DECL_OVERRIDE; + void initialize() override; + + void setGeometry(const QRect &rect) override; + QRect geometry() const 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 setWindowTitle(const QString &title) Q_DECL_OVERRIDE; - void setWindowFilePath(const QString &filePath) Q_DECL_OVERRIDE; - void setWindowIcon(const QIcon &icon) Q_DECL_OVERRIDE; - void setAlertState(bool enabled) Q_DECL_OVERRIDE; - bool isAlertState() const Q_DECL_OVERRIDE; - void raise() Q_DECL_OVERRIDE; - void lower() Q_DECL_OVERRIDE; - bool isExposed() const Q_DECL_OVERRIDE; + + void setVisible(bool visible) override; + void setWindowFlags(Qt::WindowFlags flags) override; + void setWindowState(Qt::WindowStates state) override; + void setWindowTitle(const QString &title) override; + void setWindowFilePath(const QString &filePath) override; + void setWindowIcon(const QIcon &icon) override; + void setAlertState(bool enabled) override; + bool isAlertState() const override; + void raise() override; + void lower() override; + bool isExposed() const override; bool isOpaque() const; - void propagateSizeHints() Q_DECL_OVERRIDE; - void setOpacity(qreal level) Q_DECL_OVERRIDE; - void setMask(const QRegion ®ion) Q_DECL_OVERRIDE; - bool setKeyboardGrabEnabled(bool grab) Q_DECL_OVERRIDE; - bool setMouseGrabEnabled(bool grab) Q_DECL_OVERRIDE; - QMargins frameMargins() const Q_DECL_OVERRIDE; - QSurfaceFormat format() const Q_DECL_OVERRIDE; + void propagateSizeHints() override; + void setOpacity(qreal level) override; + void setMask(const QRegion ®ion) override; + bool setKeyboardGrabEnabled(bool grab) override; + bool setMouseGrabEnabled(bool grab) override; + QMargins frameMargins() const override; + QSurfaceFormat format() const override; - bool isForeignWindow() const Q_DECL_OVERRIDE; + bool isForeignWindow() const override; - void requestActivateWindow() Q_DECL_OVERRIDE; + void requestUpdate() override; + void requestActivateWindow() override; - WId winId() const Q_DECL_OVERRIDE; - void setParent(const QPlatformWindow *window) Q_DECL_OVERRIDE; + WId winId() const override; + void setParent(const QPlatformWindow *window) override; NSView *view() const; NSWindow *nativeWindow() const; 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(); @@ -215,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(); @@ -224,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 @@ -236,17 +167,15 @@ public: QCocoaGLContext *currentContext() const; #endif - bool setWindowModified(bool modified) Q_DECL_OVERRIDE; + bool setWindowModified(bool modified) override; - void setFrameStrutEventsEnabled(bool enabled) Q_DECL_OVERRIDE; - bool frameStrutEventsEnabled() const Q_DECL_OVERRIDE + void setFrameStrutEventsEnabled(bool enabled) override; + bool frameStrutEventsEnabled() const override { return m_frameStrutEventsEnabled; } void setMenubar(QCocoaMenuBar *mb); QCocoaMenuBar *menubar() const; - NSCursor *effectiveWindowCursor() const; - void applyEffectiveWindowCursor(); void setWindowCursor(NSCursor *cursor); void registerTouch(bool enable); @@ -255,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(); + qreal devicePixelRatio() const override; QWindow *childWindowAt(QPoint windowPoint); bool shouldRefuseKeyWindowAndFirstResponder(); @@ -274,7 +199,6 @@ public: ParentChanged = 0x1, MissingWindow = 0x2, WindowModalityChanged = 0x4, - ChildNSWindowChanged = 0x8, ContentViewChanged = 0x10, PanelChanged = 0x20, }; @@ -282,20 +206,11 @@ public: Q_FLAG(RecreationReasons) protected: - bool isChildNSWindow() const; - bool isContentView() const; - - void foreachChildNSWindow(void (^block)(QCocoaWindow *)); - void recreateWindowIfNeeded(); - QCocoaNSWindow *createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel); - - QRect nativeWindowGeometry() const; - void reinsertChildWindow(QCocoaWindow *child); - void removeChildWindow(QCocoaWindow *child); + QCocoaNSWindow *createNSWindow(bool shouldBePanel); Qt::WindowState windowState() const; - void applyWindowState(Qt::WindowState newState); + void applyWindowState(Qt::WindowStates newState); void toggleMaximized(); void toggleFullScreen(); bool isTransitioningToFullScreen() const; @@ -305,24 +220,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; @@ -330,18 +255,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; @@ -362,10 +283,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 aeeef55598..562872a300 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,377 +79,7 @@ static void qt_closePopups() } } - -// @compatibility_alias doesn't work with categories or their methods -#define FullScreenProperty QT_MANGLE_NAMESPACE(FullScreenProperty) -#define qt_fullScreen QT_MANGLE_NAMESPACE(qt_fullScreen) - -@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() { @@ -480,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; + } } }]; } @@ -520,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) @@ -529,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) @@ -537,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]; } @@ -580,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() @@ -593,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(); @@ -612,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 @@ -666,7 +287,7 @@ QRect QCocoaWindow::geometry() const NSPoint windowPoint = [m_view convertPoint:NSMakePoint(0, 0) toView:nil]; NSRect screenRect = [[m_view window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; NSPoint screenPoint = screenRect.origin; - QPoint position = qt_mac_flipPoint(screenPoint).toPoint(); + QPoint position = QCocoaScreen::mapFromNative(screenPoint).toPoint(); QSize size = QRectF::fromCGRect(NSRectToCGRect([m_view bounds])).toRect().size(); return QRect(position, size); } @@ -679,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) { - NSRect bounds = qt_mac_flipRect(rect); - [m_nsWindow setFrame:[m_nsWindow frameRectForContentRect:bounds] display:YES animate:NO]; + if (isContentView()) { + NSRect bounds = QCocoaScreen::mapToNative(rect); + [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; @@ -818,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) @@ -831,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()); @@ -866,32 +382,28 @@ 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) { - QPointF localPoint = qt_mac_flipPoint([NSEvent mouseLocation]); + QPointF localPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]); QWindowSystemInterface::handleMouseEvent(window(), window()->mapFromGlobal(localPoint.toPoint()), localPoint, cocoaButton2QtButton([e buttonNumber])); }]; @@ -914,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 @@ -946,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; } } @@ -979,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; @@ -987,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 @@ -1059,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; @@ -1088,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 @@ -1117,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]]; } } @@ -1179,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]; } } } @@ -1214,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 @@ -1252,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; } @@ -1260,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); @@ -1287,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; } @@ -1330,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; } @@ -1363,7 +852,7 @@ NSView *QCocoaWindow::view() const NSWindow *QCocoaWindow::nativeWindow() const { - return m_nsWindow; + return m_view.window; } void QCocoaWindow::setEmbeddedInForeignView(bool embedded) @@ -1374,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 @@ -1384,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) + if (!isContentView()) return; - if (isChildNSWindow()) - 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; @@ -1448,6 +946,9 @@ void QCocoaWindow::windowDidBecomeKey() void QCocoaWindow::windowDidResignKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -1464,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 @@ -1509,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 @@ -1518,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() @@ -1551,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() @@ -1577,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::mapFromNative(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(lcQpaCocoaDrawing) << "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(lcQpaCocoaDrawing) << "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 @@ -1602,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 { @@ -1629,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; @@ -1673,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); } } @@ -1756,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 @@ -1786,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(lcQpaCocoaDrawing) << "QCocoaWindow::requestUpdate" << window(); + [m_view setNeedsDisplay:YES]; } void QCocoaWindow::requestActivateWindow() @@ -1818,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(); @@ -1841,7 +1378,7 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool sh rect.translate(-targetScreen->geometry().topLeft()); QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle()); - NSRect frame = NSRectFromCGRect(cocoaScreen->mapToNative(rect).toCGRect()); + NSRect frame = QCocoaScreen::mapToNative(rect, cocoaScreen); // Note: The macOS window manager has a bug, where if a screen is rotated, it will not allow // a window to be created within the area of the screen that has a Y coordinate (I quadrant) @@ -1853,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 @@ -1903,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 @@ -1923,19 +1474,6 @@ void QCocoaWindow::removeMonitor() monitor = nil; } -// Returns the current global screen geometry for the nswindow associated with this window. -QRect QCocoaWindow::nativeWindowGeometry() const -{ - if (!m_nsWindow || isChildNSWindow()) - return geometry(); - - NSRect rect = [m_nsWindow 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); - return qRect; -} - /*! Applies the given state to the NSWindow, going in/out of minimize/zoomed/fullscreen @@ -1943,13 +1481,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; @@ -1957,23 +1497,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; @@ -1999,7 +1541,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) @@ -2012,27 +1554,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 @@ -2060,22 +1606,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; } @@ -2089,51 +1625,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) @@ -2152,35 +1656,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; } @@ -2219,22 +1726,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 @@ -2248,85 +1756,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(); @@ -2376,8 +1805,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), @@ -2390,4 +1822,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..9eca4d2d43 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm +++ b/src/plugins/platforms/cocoa/qmultitouch_mac.mm @@ -39,10 +39,15 @@ #include "qmultitouch_mac_p.h" #include "qcocoahelpers.h" +#include "qcocoascreen.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; @@ -79,7 +84,7 @@ void QCocoaTouch::updateTouchData(NSTouch *nstouch, NSTouchPhase phase) if (_touchPoint.id == 0 && phase == NSTouchPhaseBegan) { _trackpadReferencePos = qnpos; - _screenReferencePos = qt_mac_flipPoint([NSEvent mouseLocation]); + _screenReferencePos = QCocoaScreen::mapFromNative([NSEvent mouseLocation]); } QPointF screenPos = _screenReferencePos; @@ -209,4 +214,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 78287b482c..216b2f1287 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -42,6 +42,7 @@ #include "qnsview.h" #include "qcocoawindow.h" #include "qcocoahelpers.h" +#include "qcocoascreen.h" #include "qmultitouch_mac_p.h" #include "qcocoadrag.h" #include "qcocoainputcontext.h" @@ -53,6 +54,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 +71,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 +113,7 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)cursorUpdate:(NSEvent *)theEvent { - [self cursorUpdate:theEvent]; + [view cursorUpdate:theEvent]; } @end @@ -133,17 +125,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 +137,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 +145,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 +175,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 +203,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 +242,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 +267,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 +278,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 +293,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 +300,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(lcQpaCocoaDrawing) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion; #ifndef QT_NO_OPENGL if (m_glContext && m_shouldSetGLContextinDrawRect) { @@ -462,85 +323,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(lcQpaCocoaDrawing) << "[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); - - // 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); + qCDebug(lcQpaCocoaDrawing) << "[QNSView updateLayer]" << m_platformWindow->window(); - 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; } @@ -623,8 +443,7 @@ static bool _q_dontOverrideCtrlLMB = false; nsWindowPoint = windowRect.origin; // NSWindow coordinates NSPoint nsViewPoint = [self convertPoint: nsWindowPoint fromView: nil]; // NSView/QWindow coordinates *qtWindowPoint = QPointF(nsViewPoint.x, nsViewPoint.y); // NSView/QWindow coordinates - - *qtScreenPoint = QPointF(mouseLocation.x, qt_mac_flipYCoordinate(mouseLocation.y)); // Qt screen coordinates + *qtScreenPoint = QCocoaScreen::mapFromNative(mouseLocation); } - (void)resetMouseButtons @@ -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; @@ -741,7 +554,7 @@ static bool _q_dontOverrideCtrlLMB = false; NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; - QPoint qtScreenPoint = QPoint(screenPoint.x, qt_mac_flipYCoordinate(screenPoint.y)); + QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); ulong timestamp = [theEvent timestamp] * 1000; QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); @@ -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 @@ -832,7 +646,7 @@ static bool _q_dontOverrideCtrlLMB = false; if (!popups->isEmpty()) { // Check if the click is outside all popups. bool inside = false; - QPointF qtScreenPoint = qt_mac_flipPoint([self screenMousePoint:theEvent]); + QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]); for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) { if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { inside = true; @@ -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,8 @@ static bool _q_dontOverrideCtrlLMB = false; if ([self hasMarkedText]) { [[NSTextInputContext currentInputContext] handleEvent:theEvent]; } else { - if (!_q_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { + auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & ctrlOrMetaModifier) { m_buttons |= Qt::RightButton; m_sendUpAsRightButton = true; } else { @@ -972,8 +787,16 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)cursorUpdate:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - m_platformWindow->applyEffectiveWindowCursor(); + qCDebug(lcQpaCocoaMouse) << "[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 +817,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 +844,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 +866,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); @@ -1243,8 +1066,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 @@ -1254,8 +1077,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 @@ -1265,8 +1088,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 @@ -1276,8 +1099,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 @@ -1307,12 +1130,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); } @@ -1322,12 +1145,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; } @@ -1344,7 +1167,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); } @@ -1353,7 +1176,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; @@ -1369,7 +1192,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); } @@ -1382,8 +1205,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); } @@ -1392,12 +1215,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 @@ -1492,15 +1315,16 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) + (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags { + const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); Qt::KeyboardModifiers qtMods =Qt::NoModifier; if (modifierFlags & NSShiftKeyMask) qtMods |= Qt::ShiftModifier; if (modifierFlags & NSControlKeyMask) - qtMods |= Qt::MetaModifier; + qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; if (modifierFlags & NSAlternateKeyMask) qtMods |= Qt::AltModifier; if (modifierFlags & NSCommandKeyMask) - qtMods |= Qt::ControlModifier; + qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; if (modifierFlags & NSNumericPadKeyMask) qtMods |= Qt::KeypadModifier; return qtMods; @@ -1533,7 +1357,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) // ALT+E to be used as a shortcut with an English keyboard even though // pressing ALT+E will give a dead key while doing normal text input. if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { - if (((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) + auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); else if ([characters length] != 0) ch = QChar([characters characterAtIndex:0]); @@ -1682,10 +1507,17 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if ((delta & mac_mask) == 0u) continue; + Qt::Key qtCode = modifier_key_symbols[i].qt_code; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + if (qtCode == Qt::Key_Meta) + qtCode = Qt::Key_Control; + else if (qtCode == Qt::Key_Control) + qtCode = Qt::Key_Meta; + } QWindowSystemInterface::handleKeyEvent(m_platformWindow->window(), timestamp, (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, - modifier_key_symbols[i].qt_code, + qtCode, qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); } } @@ -1917,14 +1749,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) // The returned rect is always based on the internal cursor. QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); - QPoint mp = m_platformWindow->window()->mapToGlobal(mr.bottomLeft()); - - NSRect rect; - rect.origin.x = mp.x(); - rect.origin.y = qt_mac_flipYCoordinate(mp.y()); - rect.size.width = mr.width(); - rect.size.height = mr.height(); - return rect; + mr.moveBottomLeft(m_platformWindow->window()->mapToGlobal(mr.bottomLeft())); + return QCocoaScreen::mapToNative(mr); } - (NSUInteger)characterIndexForPoint:(NSPoint)aPoint @@ -2129,7 +1955,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]); @@ -2173,7 +1999,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); @@ -2210,8 +2036,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin; NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; // NSView/QWindow coordinates QPoint qtWindowPoint(nsViewPoint.x, nsViewPoint.y); - - QPoint qtScreenPoint = QPoint(screenPoint.x, qt_mac_flipYCoordinate(screenPoint.y)); + QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); QWindowSystemInterface::handleMouseEvent(target, mapWindowCoordinates(m_platformWindow->window(), target, qtWindowPoint), qtScreenPoint, m_buttons); } diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h new file mode 100644 index 0000000000..ea690b69e3 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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) + +// @compatibility_alias doesn't work with categories or their methods +#define FullScreenProperty QT_MANGLE_NAMESPACE(FullScreenProperty) +#define qt_fullScreen QT_MANGLE_NAMESPACE(qt_fullScreen) + +@interface NSWindow (FullScreenProperty) +@property(readonly) BOOL qt_fullScreen; +@end + +// @compatibility_alias doesn't work with protocols +#define QNSWindowProtocol QT_MANGLE_NAMESPACE(QNSWindowProtocol) + +@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..cb13b7d184 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** 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 protocolCount; + Protocol **protocols = class_copyProtocolList(windowClass, &protocolCount); + for (unsigned int i = 0; i < protocolCount; ++i) { + Protocol *protocol = protocols[i]; + + unsigned int methodDescriptionsCount; + objc_method_description *methods = protocol_copyMethodDescriptionList( + protocol, NO, YES, &methodDescriptionsCount); + + for (unsigned int j = 0; j < methodDescriptionsCount; ++j) { + objc_method_description method = methods[j]; + class_addMethod(panelClass, method.name, + class_getMethodImplementation(windowClass, method.name), + method.types); + } + free(methods); + } + + free(protocols); +} + +- (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..6e5623d679 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 @@ -78,27 +80,14 @@ return NSRectFromCGRect(m_cocoaWindow->screen()->availableGeometry().toCGRect()); } -#if QT_MACOS_DEPLOYMENT_TARGET_BELOW(__MAC_10_11) -/* - AppKit on OS X 10.10 wrongly calls windowWillUseStandardFrame:defaultFrame - from -[NSWindow _frameForFullScreenMode] when going into fullscreen, resulting - in black bars on top and bottom of the window. By implementing the following - method, AppKit will choose that instead, and resolve the right fullscreen - geometry. -*/ -- (NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize -{ - Q_UNUSED(proposedSize); - Q_ASSERT(window == m_cocoaWindow->nativeWindow()); - return NSSizeFromCGSize(m_cocoaWindow->screen()->geometry().size().toCGSize()); -} -#endif - - (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu { 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 +96,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/cocoa/qpaintengine_mac.mm b/src/plugins/platforms/cocoa/qpaintengine_mac.mm index 7e241e3ae6..3f363b62d5 100644 --- a/src/plugins/platforms/cocoa/qpaintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qpaintengine_mac.mm @@ -108,7 +108,7 @@ CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &t) { return CGAffineTransformMake(t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy()); } -inline static QCFType<CGColorRef> cgColorForQColor(const QColor &col, QPaintDevice *pdev) +inline static QCFType<CGColorRef> cgColorForQColor(const QColor &col) { CGFloat components[] = { qt_mac_convert_color_to_cg(col.red()), @@ -116,7 +116,8 @@ inline static QCFType<CGColorRef> cgColorForQColor(const QColor &col, QPaintDevi qt_mac_convert_color_to_cg(col.blue()), qt_mac_convert_color_to_cg(col.alpha()) }; - return CGColorCreate(qt_mac_colorSpaceForDeviceType(pdev), components); + QCFType<CGColorSpaceRef> colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + return CGColorCreate(colorSpace, components); } // There's architectural problems with using native gradients @@ -239,81 +240,6 @@ static CGMutablePathRef qt_mac_compose_path(const QPainterPath &p, float off=0) return ret; } -CGColorSpaceRef QCoreGraphicsPaintEngine::m_genericColorSpace = 0; -QHash<CGDirectDisplayID, CGColorSpaceRef> QCoreGraphicsPaintEngine::m_displayColorSpaceHash; -bool QCoreGraphicsPaintEngine::m_postRoutineRegistered = false; - -CGColorSpaceRef QCoreGraphicsPaintEngine::macGenericColorSpace() -{ -#if 0 - if (!m_genericColorSpace) { - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { - m_genericColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); - } else - { - m_genericColorSpace = CGColorSpaceCreateDeviceRGB(); - } - if (!m_postRoutineRegistered) { - m_postRoutineRegistered = true; - qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); - } - } - return m_genericColorSpace; -#else - // Just return the main display colorspace for the moment. - return macDisplayColorSpace(); -#endif -} - -/* - Ideally, we should pass the widget in here, and use CGGetDisplaysWithRect() etc. - to support multiple displays correctly. -*/ -CGColorSpaceRef QCoreGraphicsPaintEngine::macDisplayColorSpace(const QWidget *widget) -{ - CGColorSpaceRef colorSpace; - - CGDirectDisplayID displayID; - if (widget == 0) { - displayID = CGMainDisplayID(); - } else { - const QRect &qrect = widget->window()->geometry(); - CGRect rect = CGRectMake(qrect.x(), qrect.y(), qrect.width(), qrect.height()); - CGDisplayCount throwAway; - CGDisplayErr dErr = CGGetDisplaysWithRect(rect, 1, &displayID, &throwAway); - if (dErr != kCGErrorSuccess) - return macDisplayColorSpace(0); // fall back on main display - } - if ((colorSpace = m_displayColorSpaceHash.value(displayID))) - return colorSpace; - - colorSpace = CGDisplayCopyColorSpace(displayID); - if (colorSpace == 0) - colorSpace = CGColorSpaceCreateDeviceRGB(); - - m_displayColorSpaceHash.insert(displayID, colorSpace); - if (!m_postRoutineRegistered) { - m_postRoutineRegistered = true; - qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); - } - return colorSpace; -} - -void QCoreGraphicsPaintEngine::cleanUpMacColorSpaces() -{ - if (m_genericColorSpace) { - CFRelease(m_genericColorSpace); - m_genericColorSpace = 0; - } - QHash<CGDirectDisplayID, CGColorSpaceRef>::const_iterator it = m_displayColorSpaceHash.constBegin(); - while (it != m_displayColorSpaceHash.constEnd()) { - if (it.value()) - CFRelease(it.value()); - ++it; - } - m_displayColorSpaceHash.clear(); -} - //pattern handling (tiling) #if 1 # define QMACPATTERN_MASK_MULTIPLIER 32 @@ -377,7 +303,7 @@ static void qt_mac_draw_pattern(void *info, CGContextRef c) QPixmap pm(w*QMACPATTERN_MASK_MULTIPLIER, h*QMACPATTERN_MASK_MULTIPLIER); pm.fill(c0); QMacCGContext pm_ctx(&pm); - CGContextSetFillColorWithColor(c, cgColorForQColor(c1, pat->pdev)); + CGContextSetFillColorWithColor(c, cgColorForQColor(c1)); CGRect rect = CGRectMake(0, 0, w, h); for (int x = 0; x < QMACPATTERN_MASK_MULTIPLIER; ++x) { rect.origin.x = x * w; @@ -409,7 +335,7 @@ static void qt_mac_draw_pattern(void *info, CGContextRef c) bool needRestore = false; if (CGImageIsMask(pat->image)) { CGContextSaveGState(c); - CGContextSetFillColorWithColor(c, cgColorForQColor(pat->foreground, pat->pdev)); + CGContextSetFillColorWithColor(c, cgColorForQColor(pat->foreground)); } CGRect rect = CGRectMake(0, 0, w, h); qt_mac_drawCGImage(c, &rect, pat->image); @@ -871,7 +797,7 @@ void QCoreGraphicsPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, co d->saveGraphicsState(); const QColor &col = d->current.pen.color(); - CGContextSetFillColorWithColor(d->hd, cgColorForQColor(col, d->pdev)); + CGContextSetFillColorWithColor(d->hd, cgColorForQColor(col)); image = qt_mac_create_imagemask(pm, sr); } else if (differentSize) { QCFType<CGImageRef> img = qt_mac_toCGImage(pm.toImage()); @@ -1233,7 +1159,7 @@ QCoreGraphicsPaintEnginePrivate::setStrokePen(const QPen &pen) CGContextSetLineDash(hd, pen.dashOffset() * cglinewidth, linedashes.data(), linedashes.size()); // color - CGContextSetStrokeColorWithColor(hd, cgColorForQColor(pen.color(), pdev)); + CGContextSetStrokeColorWithColor(hd, cgColorForQColor(pen.color())); } // Add our own patterns here to deal with the fact that the coordinate system @@ -1276,7 +1202,7 @@ void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) CGFunctionRef fill_func = CGFunctionCreate(reinterpret_cast<void *>(¤t.brush), 1, domain, 4, 0, &callbacks); - CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev); + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB) if (bs == Qt::LinearGradientPattern) { const QLinearGradient *linearGrad = static_cast<const QLinearGradient *>(grad); const QPointF start(linearGrad->start()); @@ -1315,7 +1241,7 @@ void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) components[0] = qt_mac_convert_color_to_cg(col.red()); components[1] = qt_mac_convert_color_to_cg(col.green()); components[2] = qt_mac_convert_color_to_cg(col.blue()); - base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + base_colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); } } else { qpattern->as_mask = true; @@ -1325,7 +1251,7 @@ void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) components[0] = qt_mac_convert_color_to_cg(col.red()); components[1] = qt_mac_convert_color_to_cg(col.green()); components[2] = qt_mac_convert_color_to_cg(col.blue()); - base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + base_colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); } int width = qpattern->width(), height = qpattern->height(); qpattern->foreground = current.brush.color(); @@ -1346,10 +1272,12 @@ void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) !base_colorspace, &callbks); CGContextSetFillPattern(hd, fill_pattern, components); + CGPatternRelease(fill_pattern); + CGColorSpaceRelease(base_colorspace); CGColorSpaceRelease(fill_colorspace); } else if (bs != Qt::NoBrush) { - CGContextSetFillColorWithColor(hd, cgColorForQColor(current.brush.color(), pdev)); + CGContextSetFillColorWithColor(hd, cgColorForQColor(current.brush.color())); } } diff --git a/src/plugins/platforms/cocoa/qpaintengine_mac_p.h b/src/plugins/platforms/cocoa/qpaintengine_mac_p.h index 57d985c399..c9519ac827 100644 --- a/src/plugins/platforms/cocoa/qpaintengine_mac_p.h +++ b/src/plugins/platforms/cocoa/qpaintengine_mac_p.h @@ -73,8 +73,6 @@ public: bool begin(QPaintDevice *pdev); bool end(); - static CGColorSpaceRef macGenericColorSpace(); - static CGColorSpaceRef macDisplayColorSpace(const QWidget *widget = 0); void updateState(const QPaintEngineState &state); @@ -126,10 +124,6 @@ protected: QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr); private: - static bool m_postRoutineRegistered; - static CGColorSpaceRef m_genericColorSpace; - static QHash<CGDirectDisplayID, CGColorSpaceRef> m_displayColorSpaceHash; - static void cleanUpMacColorSpaces(); Q_DISABLE_COPY(QCoreGraphicsPaintEngine) }; diff --git a/src/plugins/platforms/cocoa/qprintengine_mac.mm b/src/plugins/platforms/cocoa/qprintengine_mac.mm index c39af870d4..b3d48c1ec3 100644 --- a/src/plugins/platforms/cocoa/qprintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qprintengine_mac.mm @@ -247,7 +247,7 @@ void QMacPrintEnginePrivate::initialize() QList<int> resolutions = m_printDevice->supportedResolutions(); if (!resolutions.isEmpty() && mode != QPrinter::ScreenResolution) { - qSort(resolutions); + std::sort(resolutions.begin(), resolutions.end()); if (resolutions.count() > 1 && mode == QPrinter::HighResolution) resolution.hRes = resolution.vRes = resolutions.last(); else diff --git a/src/plugins/platforms/cocoa/qprintengine_mac_p.h b/src/plugins/platforms/cocoa/qprintengine_mac_p.h index 2d46a250d5..9514f3e691 100644 --- a/src/plugins/platforms/cocoa/qprintengine_mac_p.h +++ b/src/plugins/platforms/cocoa/qprintengine_mac_p.h @@ -150,8 +150,8 @@ public: PMPrintSession session() const { return static_cast<PMPrintSession>([printInfo PMPrintSession]); } PMPrintSettings settings() const { return static_cast<PMPrintSettings>([printInfo PMPrintSettings]); } - QPaintEngine *aggregateEngine() Q_DECL_OVERRIDE { return paintEngine; } - Qt::HANDLE nativeHandle() Q_DECL_OVERRIDE { return q_func()->handle(); } + QPaintEngine *aggregateEngine() override { return paintEngine; } + Qt::HANDLE nativeHandle() override { return q_func()->handle(); } }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/direct2d/direct2d.pro b/src/plugins/platforms/direct2d/direct2d.pro index 3fe0d5e660..3bfd02bdc8 100644 --- a/src/plugins/platforms/direct2d/direct2d.pro +++ b/src/plugins/platforms/direct2d/direct2d.pro @@ -6,8 +6,9 @@ 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 +LIBS += -ldwmapi -ld2d1 -ld3d11 -ldwrite -lversion -lgdi32 include(../windows/windows.pri) diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h b/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h index 670c4e9840..f72ea2b038 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dbackingstore.h @@ -54,12 +54,12 @@ public: QWindowsDirect2DBackingStore(QWindow *window); ~QWindowsDirect2DBackingStore(); - void beginPaint(const QRegion &) Q_DECL_OVERRIDE; - void endPaint() Q_DECL_OVERRIDE; + void beginPaint(const QRegion &) override; + void endPaint() override; - QPaintDevice *paintDevice() Q_DECL_OVERRIDE; - void flush(QWindow *targetWindow, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - void resize(const QSize &size, const QRegion &staticContents) Q_DECL_OVERRIDE; + QPaintDevice *paintDevice() override; + void flush(QWindow *targetWindow, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; QImage toImage() const override; }; diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2ddevicecontext.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2ddevicecontext.cpp index 643ae877d0..d578a58982 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2ddevicecontext.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2ddevicecontext.cpp @@ -161,7 +161,7 @@ void QWindowsDirect2DDeviceContextSuspender::resume() { if (m_dc) { m_dc->resume(); - m_dc = Q_NULLPTR; + m_dc = nullptr; } } diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.cpp index ea51135583..6d98da5be5 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.cpp @@ -48,6 +48,7 @@ #include <qplatformdefs.h> #include <QtCore/QCoreApplication> +#include <QtCore/QVersionNumber> #include <QtGui/private/qpixmap_raster_p.h> #include <QtGui/qpa/qwindowsysteminterface.h> #include <QtEventDispatcherSupport/private/qwindowsguieventdispatcher_p.h> @@ -73,127 +74,60 @@ public: QWindowsDirect2DContext m_d2dContext; }; -class Direct2DVersion +static QVersionNumber systemD2DVersion() { -private: - Direct2DVersion() = default; - - Direct2DVersion(int one, int two, int three, int four) - : partOne(one) - , partTwo(two) - , partThree(three) - , partFour(four) - {} + static const int bufSize = 512; + TCHAR filename[bufSize]; + + UINT i = GetSystemDirectory(filename, bufSize); + if (i > 0 && i < bufSize) { + if (_tcscat_s(filename, bufSize, __TEXT("\\d2d1.dll")) == 0) { + DWORD versionInfoSize = GetFileVersionInfoSize(filename, NULL); + if (versionInfoSize) { + QVarLengthArray<BYTE> info(static_cast<int>(versionInfoSize)); + if (GetFileVersionInfo(filename, 0, versionInfoSize, info.data())) { + UINT size; + DWORD *fi; + + if (VerQueryValue(info.constData(), __TEXT("\\"), + reinterpret_cast<void **>(&fi), &size) && size) { + const VS_FIXEDFILEINFO *verInfo = reinterpret_cast<const VS_FIXEDFILEINFO *>(fi); + return QVersionNumber{HIWORD(verInfo->dwFileVersionMS), LOWORD(verInfo->dwFileVersionMS), + HIWORD(verInfo->dwFileVersionLS), LOWORD(verInfo->dwFileVersionLS)}; + } + } + } + } + } + return QVersionNumber(); +} -public: +static QVersionNumber minimumD2DVersion() +{ // 6.2.9200.16492 corresponds to Direct2D 1.1 on Windows 7 SP1 with Platform Update - enum { + enum : int { D2DMinVersionPart1 = 6, D2DMinVersionPart2 = 2, D2DMinVersionPart3 = 9200, D2DMinVersionPart4 = 16492 }; - static Direct2DVersion systemVersion() { - static const int bufSize = 512; - TCHAR filename[bufSize]; - - UINT i = GetSystemDirectory(filename, bufSize); - if (i > 0 && i < bufSize) { - if (_tcscat_s(filename, bufSize, __TEXT("\\d2d1.dll")) == 0) { - DWORD versionInfoSize = GetFileVersionInfoSize(filename, NULL); - if (versionInfoSize) { - QVarLengthArray<BYTE> info(static_cast<int>(versionInfoSize)); - if (GetFileVersionInfo(filename, 0, versionInfoSize, info.data())) { - UINT size; - DWORD *fi; - - if (VerQueryValue(info.constData(), __TEXT("\\"), - reinterpret_cast<void **>(&fi), &size) && size) { - const VS_FIXEDFILEINFO *verInfo = reinterpret_cast<const VS_FIXEDFILEINFO *>(fi); - return Direct2DVersion(HIWORD(verInfo->dwFileVersionMS), - LOWORD(verInfo->dwFileVersionMS), - HIWORD(verInfo->dwFileVersionLS), - LOWORD(verInfo->dwFileVersionLS)); - } - } - } - } - } - - return Direct2DVersion(); - } - - static Direct2DVersion minimumVersion() { - return Direct2DVersion(D2DMinVersionPart1, - D2DMinVersionPart2, - D2DMinVersionPart3, - D2DMinVersionPart4); - } - - bool isValid() const { - return partOne || partTwo || partThree || partFour; - } - - bool operator<(const Direct2DVersion &other) { - int c = cmp(partOne, other.partOne); - if (c > 0) - return false; - if (c < 0) - return true; - - c = cmp(partTwo, other.partTwo); - if (c > 0) - return false; - if (c < 0) - return true; - - c = cmp(partThree, other.partThree); - if (c > 0) - return false; - if (c < 0) - return true; - - c = cmp(partFour, other.partFour); - if (c > 0) - return false; - if (c < 0) - return true; - - return false; - } - - static Q_DECL_CONSTEXPR int cmp(int a, int b) { - return a - b; - } - - int partOne = 0; - int partTwo = 0; - int partThree = 0; - int partFour = 0; -}; + return QVersionNumber{D2DMinVersionPart1, D2DMinVersionPart2, D2DMinVersionPart3, D2DMinVersionPart4}; +} QWindowsDirect2DIntegration *QWindowsDirect2DIntegration::create(const QStringList ¶mList) { - Direct2DVersion systemVersion = Direct2DVersion::systemVersion(); - - if (systemVersion.isValid() && systemVersion < Direct2DVersion::minimumVersion()) { + const QVersionNumber systemVersion = systemD2DVersion(); + const QVersionNumber minimumVersion = minimumD2DVersion(); + if (!systemVersion.isNull() && systemVersion < minimumVersion) { QString msg = QCoreApplication::translate("QWindowsDirect2DIntegration", "Qt cannot load the direct2d platform plugin because " \ "the Direct2D version on this system is too old. The " \ "minimum system requirement for this platform plugin " \ "is Windows 7 SP1 with Platform Update.\n\n" \ - "The minimum Direct2D version required is %1.%2.%3.%4. " \ - "The Direct2D version on this system is %5.%6.%7.%8."); - - msg = msg.arg(Direct2DVersion::D2DMinVersionPart1) - .arg(Direct2DVersion::D2DMinVersionPart2) - .arg(Direct2DVersion::D2DMinVersionPart3) - .arg(Direct2DVersion::D2DMinVersionPart4) - .arg(systemVersion.partOne) - .arg(systemVersion.partTwo) - .arg(systemVersion.partThree) - .arg(systemVersion.partFour); + "The minimum Direct2D version required is %1. " \ + "The Direct2D version on this system is %2.") + .arg(minimumVersion.toString(), systemVersion.toString()); QString caption = QCoreApplication::translate("QWindowsDirect2DIntegration", "Cannot load direct2d platform plugin"); @@ -203,7 +137,7 @@ QWindowsDirect2DIntegration *QWindowsDirect2DIntegration::create(const QStringLi caption.toStdWString().c_str(), MB_OK | MB_ICONERROR); - return Q_NULLPTR; + return nullptr; } QWindowsDirect2DIntegration *integration = new QWindowsDirect2DIntegration(paramList); diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.h b/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.h index 43f2a08745..39ca1d0dbf 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dintegration.h @@ -58,15 +58,15 @@ public: static QWindowsDirect2DIntegration *instance(); - QPlatformNativeInterface *nativeInterface() const Q_DECL_OVERRIDE; - QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QPlatformNativeInterface *nativeInterface() const override; + QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; + QAbstractEventDispatcher *createEventDispatcher() const override; QWindowsDirect2DContext *direct2DContext() const; protected: - QWindowsWindow *createPlatformWindowHelper(QWindow *window, const QWindowsWindowData &) const Q_DECL_OVERRIDE; + QWindowsWindow *createPlatformWindowHelper(QWindow *window, const QWindowsWindowData &) const override; private: explicit QWindowsDirect2DIntegration(const QStringList ¶mList); diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dnativeinterface.h b/src/plugins/platforms/direct2d/qwindowsdirect2dnativeinterface.h index d57136129b..fce6ff9969 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dnativeinterface.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dnativeinterface.h @@ -48,7 +48,7 @@ class QWindowsDirect2DNativeInterface : public QWindowsNativeInterface { Q_OBJECT public: - void *nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *bs) Q_DECL_OVERRIDE; + void *nativeResourceForBackingStore(const QByteArray &resource, QBackingStore *bs) override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.h b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.h index f434ef993c..1f23d604f5 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.h @@ -58,11 +58,11 @@ public: QWindowsDirect2DPaintEngine::Flags paintFlags = QWindowsDirect2DPaintEngine::NoFlag); ~QWindowsDirect2DPaintDevice(); - QPaintEngine *paintEngine() const Q_DECL_OVERRIDE; - int devType() const Q_DECL_OVERRIDE; + QPaintEngine *paintEngine() const override; + int devType() const override; protected: - int metric(PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + int metric(PaintDeviceMetric metric) const override; private: QScopedPointer<QWindowsDirect2DPaintDevicePrivate> d_ptr; diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp index 164429ba30..95fbd37247 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp @@ -804,7 +804,7 @@ public: const bool alias = !q->antiAliasingEnabled(); QVectorPath::CacheEntry *cacheEntry = path.isCacheable() ? path.lookupCacheData(q) - : Q_NULLPTR; + : nullptr; if (cacheEntry) { D2DVectorPathCache *e = static_cast<D2DVectorPathCache *>(cacheEntry->data); diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.h b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.h index a8f63af5d5..b9616acd6a 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.h @@ -65,45 +65,45 @@ public: QWindowsDirect2DPaintEngine(QWindowsDirect2DBitmap *bitmap, Flags flags); - bool begin(QPaintDevice *pdev) Q_DECL_OVERRIDE; - bool end() Q_DECL_OVERRIDE; + bool begin(QPaintDevice *pdev) override; + bool end() override; - Type type() const Q_DECL_OVERRIDE; + Type type() const override; - void setState(QPainterState *s) Q_DECL_OVERRIDE; + void setState(QPainterState *s) override; - void draw(const QVectorPath &path) Q_DECL_OVERRIDE; + void draw(const QVectorPath &path) override; - void fill(const QVectorPath &path, const QBrush &brush) Q_DECL_OVERRIDE; + void fill(const QVectorPath &path, const QBrush &brush) override; void fill(ID2D1Geometry *geometry, const QBrush &brush); - void stroke(const QVectorPath &path, const QPen &pen) Q_DECL_OVERRIDE; + void stroke(const QVectorPath &path, const QPen &pen) override; void stroke(ID2D1Geometry *geometry, const QPen &pen); - void clip(const QVectorPath &path, Qt::ClipOperation op) Q_DECL_OVERRIDE; + void clip(const QVectorPath &path, Qt::ClipOperation op) override; - void clipEnabledChanged() Q_DECL_OVERRIDE; - void penChanged() Q_DECL_OVERRIDE; - void brushChanged() Q_DECL_OVERRIDE; - void brushOriginChanged() Q_DECL_OVERRIDE; - void opacityChanged() Q_DECL_OVERRIDE; - void compositionModeChanged() Q_DECL_OVERRIDE; - void renderHintsChanged() Q_DECL_OVERRIDE; - void transformChanged() Q_DECL_OVERRIDE; + void clipEnabledChanged() override; + void penChanged() override; + void brushChanged() override; + void brushOriginChanged() override; + void opacityChanged() override; + void compositionModeChanged() override; + void renderHintsChanged() override; + void transformChanged() override; - void fillRect(const QRectF &rect, const QBrush &brush) Q_DECL_OVERRIDE; + void fillRect(const QRectF &rect, const QBrush &brush) override; - void drawRects(const QRect *rects, int rectCount) Q_DECL_OVERRIDE; - void drawRects(const QRectF *rects, int rectCount) Q_DECL_OVERRIDE; + void drawRects(const QRect *rects, int rectCount) override; + void drawRects(const QRectF *rects, int rectCount) override; - void drawEllipse(const QRectF &r) Q_DECL_OVERRIDE; - void drawEllipse(const QRect &r) Q_DECL_OVERRIDE; + void drawEllipse(const QRectF &r) override; + void drawEllipse(const QRect &r) override; - void drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE; - void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) Q_DECL_OVERRIDE; + void drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags = Qt::AutoColor) override; + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override; - void drawStaticTextItem(QStaticTextItem *staticTextItem) Q_DECL_OVERRIDE; - void drawTextItem(const QPointF &p, const QTextItem &textItem) Q_DECL_OVERRIDE; + void drawStaticTextItem(QStaticTextItem *staticTextItem) override; + void drawTextItem(const QPointF &p, const QTextItem &textItem) override; private: void ensureBrush(); diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dplatformpixmap.h b/src/plugins/platforms/direct2d/qwindowsdirect2dplatformpixmap.h index 5f65a2313a..0448613a95 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dplatformpixmap.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dplatformpixmap.h @@ -59,21 +59,21 @@ public: QWindowsDirect2DPlatformPixmap(PixelType pixelType, QWindowsDirect2DPaintEngine::Flags flags, QWindowsDirect2DBitmap *bitmap); ~QWindowsDirect2DPlatformPixmap(); - void resize(int width, int height) Q_DECL_OVERRIDE; - void fromImage(const QImage &image, Qt::ImageConversionFlags flags) Q_DECL_OVERRIDE; + void resize(int width, int height) override; + void fromImage(const QImage &image, Qt::ImageConversionFlags flags) override; - int metric(QPaintDevice::PaintDeviceMetric metric) const Q_DECL_OVERRIDE; - void fill(const QColor &color) Q_DECL_OVERRIDE; + int metric(QPaintDevice::PaintDeviceMetric metric) const override; + void fill(const QColor &color) override; - bool hasAlphaChannel() const Q_DECL_OVERRIDE; + bool hasAlphaChannel() const override; - QImage toImage() const Q_DECL_OVERRIDE; - QImage toImage(const QRect &rect) const Q_DECL_OVERRIDE; + QImage toImage() const override; + QImage toImage(const QRect &rect) const override; - QPaintEngine* paintEngine() const Q_DECL_OVERRIDE; + QPaintEngine* paintEngine() const override; - qreal devicePixelRatio() const Q_DECL_OVERRIDE; - void setDevicePixelRatio(qreal scaleFactor) Q_DECL_OVERRIDE; + qreal devicePixelRatio() const override; + void setDevicePixelRatio(qreal scaleFactor) override; QWindowsDirect2DBitmap *bitmap() const; diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp index 21294cfb15..f81182e0b1 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.cpp @@ -209,7 +209,7 @@ void QWindowsDirect2DWindow::resizeSwapChain(const QSize &size) { m_pixmap.reset(); m_bitmap.reset(); - m_deviceContext->SetTarget(Q_NULLPTR); + m_deviceContext->SetTarget(nullptr); m_needsFullFlush = true; if (!m_swapChain) @@ -241,7 +241,7 @@ QSharedPointer<QWindowsDirect2DBitmap> QWindowsDirect2DWindow::copyBackBuffer() dpiX, // FLOAT dpiX; dpiY, // FLOAT dpiY; D2D1_BITMAP_OPTIONS_TARGET, // D2D1_BITMAP_OPTIONS bitmapOptions; - Q_NULLPTR // _Field_size_opt_(1) ID2D1ColorContext *colorContext; + nullptr // _Field_size_opt_(1) ID2D1ColorContext *colorContext; }; ComPtr<ID2D1Bitmap1> copy; HRESULT hr = m_deviceContext.Get()->CreateBitmap(size, NULL, 0, properties, ©); @@ -257,7 +257,7 @@ QSharedPointer<QWindowsDirect2DBitmap> QWindowsDirect2DWindow::copyBackBuffer() return null_result; } - return QSharedPointer<QWindowsDirect2DBitmap>(new QWindowsDirect2DBitmap(copy.Get(), Q_NULLPTR)); + return QSharedPointer<QWindowsDirect2DBitmap>(new QWindowsDirect2DBitmap(copy.Get(), nullptr)); } void QWindowsDirect2DWindow::setupBitmap() diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.h b/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.h index 156d4660d1..3ac532d938 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.h +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dwindow.h @@ -56,7 +56,7 @@ public: QWindowsDirect2DWindow(QWindow *window, const QWindowsWindowData &data); ~QWindowsDirect2DWindow(); - void setWindowFlags(Qt::WindowFlags flags) Q_DECL_OVERRIDE; + void setWindowFlags(Qt::WindowFlags flags) override; QPixmap *pixmap(); void flush(QWindowsDirect2DBitmap *bitmap, const QRegion ®ion, const QPoint &offset); diff --git a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp index e411ea55e9..1b8f50a1c6 100644 --- a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp @@ -103,7 +103,7 @@ QStringList QEglFSDeviceIntegrationFactory::keys(const QString &pluginPath) QEglFSDeviceIntegration *QEglFSDeviceIntegrationFactory::create(const QString &key, const QString &pluginPath) { - QEglFSDeviceIntegration *integration = Q_NULLPTR; + QEglFSDeviceIntegration *integration = nullptr; #if QT_CONFIG(library) if (!pluginPath.isEmpty()) { QCoreApplication::addLibraryPath(pluginPath); @@ -351,9 +351,28 @@ bool QEglFSDeviceIntegration::supportsSurfacelessContexts() const return true; } +QFunctionPointer QEglFSDeviceIntegration::platformFunction(const QByteArray &function) const +{ + Q_UNUSED(function); + return nullptr; +} + +void *QEglFSDeviceIntegration::nativeResourceForIntegration(const QByteArray &name) +{ + Q_UNUSED(name); + return nullptr; +} + +void *QEglFSDeviceIntegration::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) +{ + Q_UNUSED(resource); + Q_UNUSED(screen); + return nullptr; +} + void *QEglFSDeviceIntegration::wlDisplay() const { - return Q_NULLPTR; + return nullptr; } EGLConfig QEglFSDeviceIntegration::chooseConfig(EGLDisplay display, const QSurfaceFormat &format) diff --git a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration_p.h b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration_p.h index 4335554912..71ffb4c69a 100644 --- a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration_p.h +++ b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration_p.h @@ -103,7 +103,9 @@ public: virtual int framebufferIndex() const; virtual bool supportsPBuffers() const; virtual bool supportsSurfacelessContexts() const; - + virtual QFunctionPointer platformFunction(const QByteArray &function) const; + virtual void *nativeResourceForIntegration(const QByteArray &name); + virtual void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen); virtual void *wlDisplay() const; static EGLConfig chooseConfig(EGLDisplay display, const QSurfaceFormat &format); diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp index 9a0be489a8..33878a5f50 100644 --- a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp @@ -324,13 +324,14 @@ void *QEglFSIntegration::nativeResourceForIntegration(const QByteArray &resource result = qt_egl_device_integration()->wlDisplay(); break; default: + result = qt_egl_device_integration()->nativeResourceForIntegration(resource); break; } return result; } -void *QEglFSIntegration::nativeResourceForScreen(const QByteArray &resource, QScreen *) +void *QEglFSIntegration::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) { void *result = 0; @@ -341,6 +342,7 @@ void *QEglFSIntegration::nativeResourceForScreen(const QByteArray &resource, QSc result = reinterpret_cast<void*>(nativeDisplay()); break; default: + result = qt_egl_device_integration()->nativeResourceForScreen(resource, screen); break; } @@ -427,11 +429,9 @@ QFunctionPointer QEglFSIntegration::platformFunction(const QByteArray &function) #if QT_CONFIG(evdev) if (function == QEglFSFunctions::loadKeymapTypeIdentifier()) return QFunctionPointer(loadKeymapStatic); -#else - Q_UNUSED(function) #endif - return 0; + return qt_egl_device_integration()->platformFunction(function); } void QEglFSIntegration::loadKeymapStatic(const QString &filename) diff --git a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp index 17e4aa1df8..29cfd4ea79 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/api/qeglfswindow_p.h b/src/plugins/platforms/eglfs/api/qeglfswindow_p.h index 6bda262523..c61f04f569 100644 --- a/src/plugins/platforms/eglfs/api/qeglfswindow_p.h +++ b/src/plugins/platforms/eglfs/api/qeglfswindow_p.h @@ -95,7 +95,7 @@ public: EGLNativeWindowType eglWindow() const; EGLSurface surface() const; - QEglFSScreen *screen() const; + QEglFSScreen *screen() const override; bool hasNativeWindow() const { return m_flags.testFlag(HasNativeWindow); } diff --git a/src/plugins/platforms/eglfs/deviceintegration/deviceintegration.pro b/src/plugins/platforms/eglfs/deviceintegration/deviceintegration.pro index 92ee83fd8f..919ecd01f6 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/deviceintegration.pro +++ b/src/plugins/platforms/eglfs/deviceintegration/deviceintegration.pro @@ -4,6 +4,7 @@ QT_FOR_CONFIG += gui-private qtConfig(egl_x11): SUBDIRS += eglfs_x11 qtConfig(eglfs_gbm): SUBDIRS *= eglfs_kms_support eglfs_kms qtConfig(eglfs_egldevice): SUBDIRS *= eglfs_kms_support eglfs_kms_egldevice +qtConfig(eglfs_vsp2): SUBDIRS += eglfs_kms_vsp2 qtConfig(eglfs_brcm): SUBDIRS += eglfs_brcm qtConfig(eglfs_mali): SUBDIRS += eglfs_mali qtConfig(eglfs_viv): SUBDIRS += eglfs_viv 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..43170a3875 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 @@ -19,11 +19,13 @@ SOURCES += $$PWD/qeglfskmsgbmmain.cpp \ $$PWD/qeglfskmsgbmintegration.cpp \ $$PWD/qeglfskmsgbmdevice.cpp \ $$PWD/qeglfskmsgbmscreen.cpp \ - $$PWD/qeglfskmsgbmcursor.cpp + $$PWD/qeglfskmsgbmcursor.cpp \ + $$PWD/qeglfskmsgbmwindow.cpp HEADERS += $$PWD/qeglfskmsgbmintegration.h \ $$PWD/qeglfskmsgbmdevice.h \ $$PWD/qeglfskmsgbmscreen.h \ - $$PWD/qeglfskmsgbmcursor.h + $$PWD/qeglfskmsgbmcursor.h \ + $$PWD/qeglfskmsgbmwindow.h OTHER_FILES += $$PWD/eglfs_kms.json diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp index 19790e5c45..9bd7fee1fb 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp @@ -68,7 +68,7 @@ Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) QEglFSKmsGbmCursor::QEglFSKmsGbmCursor(QEglFSKmsGbmScreen *screen) : m_screen(screen) , m_cursorSize(64, 64) // 64x64 is the old standard size, we now try to query the real size below - , m_bo(Q_NULLPTR) + , m_bo(nullptr) , m_cursorImage(0, 0, 0, 0, 0, 0) , m_state(CursorPendingVisible) { @@ -118,7 +118,7 @@ QEglFSKmsGbmCursor::~QEglFSKmsGbmCursor() if (m_bo) { gbm_bo_destroy(m_bo); - m_bo = Q_NULLPTR; + m_bo = nullptr; } } @@ -132,7 +132,7 @@ void QEglFSKmsGbmCursor::updateMouseStatus() m_state = visible ? CursorPendingVisible : CursorPendingHidden; #ifndef QT_NO_CURSOR - changeCursor(Q_NULLPTR, m_screen->topLevelAt(pos())); + changeCursor(nullptr, m_screen->topLevelAt(pos())); #endif } @@ -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/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp index e218d580a2..20127ae7f7 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp @@ -53,28 +53,17 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) -void QEglFSKmsGbmDevice::pageFlipHandler(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *user_data) -{ - Q_UNUSED(fd); - Q_UNUSED(sequence); - Q_UNUSED(tv_sec); - Q_UNUSED(tv_usec); - - QEglFSKmsScreen *screen = static_cast<QEglFSKmsScreen *>(user_data); - screen->flipFinished(); -} - QEglFSKmsGbmDevice::QEglFSKmsGbmDevice(QKmsScreenConfig *screenConfig, const QString &path) : QEglFSKmsDevice(screenConfig, path) - , m_gbm_device(Q_NULLPTR) - , m_globalCursor(Q_NULLPTR) + , m_gbm_device(nullptr) + , m_globalCursor(nullptr) { } bool QEglFSKmsGbmDevice::open() { Q_ASSERT(fd() == -1); - Q_ASSERT(m_gbm_device == Q_NULLPTR); + Q_ASSERT(m_gbm_device == nullptr); int fd = qt_safe_open(devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC); if (fd == -1) { @@ -103,7 +92,7 @@ void QEglFSKmsGbmDevice::close() if (m_gbm_device) { gbm_device_destroy(m_gbm_device); - m_gbm_device = Q_NULLPTR; + m_gbm_device = nullptr; } if (fd() != -1) { @@ -134,24 +123,13 @@ void QEglFSKmsGbmDevice::destroyGlobalCursor() if (m_globalCursor) { qCDebug(qLcEglfsKmsDebug, "Destroying global GBM mouse cursor"); delete m_globalCursor; - m_globalCursor = Q_NULLPTR; + m_globalCursor = nullptr; } } -void QEglFSKmsGbmDevice::handleDrmEvent() -{ - drmEventContext drmEvent; - memset(&drmEvent, 0, sizeof(drmEvent)); - drmEvent.version = 2; - drmEvent.vblank_handler = nullptr; - drmEvent.page_flip_handler = pageFlipHandler; - - drmHandleEvent(fd(), &drmEvent); -} - QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output) { - QEglFSKmsGbmScreen *screen = new QEglFSKmsGbmScreen(this, output); + QEglFSKmsGbmScreen *screen = new QEglFSKmsGbmScreen(this, output, false); if (!m_globalCursor && screenConfig()->hwCursor()) { qCDebug(qLcEglfsKmsDebug, "Creating new global GBM mouse cursor"); @@ -161,4 +139,20 @@ QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output) return screen; } +QPlatformScreen *QEglFSKmsGbmDevice::createHeadlessScreen() +{ + return new QEglFSKmsGbmScreen(this, QKmsOutput(), true); +} + +void QEglFSKmsGbmDevice::registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector<QPlatformScreen *> &screensCloningThisScreen) +{ + if (!screenThisScreenClones && screensCloningThisScreen.isEmpty()) + return; + + QEglFSKmsGbmScreen *gbmScreen = static_cast<QEglFSKmsGbmScreen *>(screen); + gbmScreen->initCloning(screenThisScreenClones, screensCloningThisScreen); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h index 08ca28d48e..518e2ce58b 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h @@ -65,9 +65,11 @@ public: QPlatformCursor *globalCursor() const; void destroyGlobalCursor(); - void handleDrmEvent(); - QPlatformScreen *createScreen(const QKmsOutput &output) override; + QPlatformScreen *createHeadlessScreen() override; + void registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector<QPlatformScreen *> &screensCloningThisScreen) override; private: Q_DISABLE_COPY(QEglFSKmsGbmDevice) @@ -75,12 +77,6 @@ private: gbm_device *m_gbm_device; QEglFSKmsGbmCursor *m_globalCursor; - - static void pageFlipHandler(int fd, - unsigned int sequence, - unsigned int tv_sec, - unsigned int tv_usec, - void *user_data); }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp index b6cdcf92b6..402338197d 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmintegration.cpp @@ -43,19 +43,13 @@ #include "qeglfskmsgbmdevice.h" #include "qeglfskmsgbmscreen.h" #include "qeglfskmsgbmcursor.h" +#include "qeglfskmsgbmwindow.h" #include "private/qeglfscursor_p.h" -#include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h> #include <QtCore/QLoggingCategory> -#include <QtCore/QJsonDocument> -#include <QtCore/QJsonObject> -#include <QtCore/QJsonArray> -#include <QtGui/qpa/qplatformwindow.h> -#include <QtGui/qpa/qplatformcursor.h> #include <QtGui/QScreen> +#include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h> -#include <xf86drm.h> -#include <xf86drmMode.h> #include <gbm.h> QT_BEGIN_NAMESPACE @@ -67,20 +61,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) @@ -88,7 +97,6 @@ EGLNativeWindowType QEglFSKmsGbmIntegration::createNativeOffscreenWindow(const Q Q_UNUSED(format); Q_ASSERT(device()); - qCDebug(qLcEglfsKmsDebug) << "Creating native off screen window"; gbm_surface *surface = gbm_surface_create(static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice(), 1, 1, GBM_FORMAT_XRGB8888, @@ -117,8 +125,7 @@ QPlatformCursor *QEglFSKmsGbmIntegration::createCursor(QPlatformScreen *screen) void QEglFSKmsGbmIntegration::presentBuffer(QPlatformSurface *surface) { QWindow *window = static_cast<QWindow *>(surface->surface()); - QEglFSKmsScreen *screen = static_cast<QEglFSKmsScreen *>(window->screen()->handle()); - + QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(window->screen()->handle()); screen->flip(); } @@ -143,4 +150,9 @@ QKmsDevice *QEglFSKmsGbmIntegration::createDevice() return new QEglFSKmsGbmDevice(screenConfig(), path); } +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/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index 87fb3146c7..4742143121 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** +** Copyright (C) 2017 The Qt Company Ltd. ** Copyright (C) 2015 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/ ** @@ -55,6 +55,18 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) +static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) +{ + Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); + return drmFormat; +} + +static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat) +{ + Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); + return gbmFormat; +} + void QEglFSKmsGbmScreen::bufferDestroyedHandler(gbm_bo *bo, void *data) { FrameBuffer *fb = static_cast<FrameBuffer *>(data); @@ -77,29 +89,34 @@ QEglFSKmsGbmScreen::FrameBuffer *QEglFSKmsGbmScreen::framebufferForBufferObject( uint32_t width = gbm_bo_get_width(bo); uint32_t height = gbm_bo_get_height(bo); - uint32_t stride = gbm_bo_get_stride(bo); - uint32_t handle = gbm_bo_get_handle(bo).u32; + uint32_t handles[4] = { gbm_bo_get_handle(bo).u32 }; + uint32_t strides[4] = { gbm_bo_get_stride(bo) }; + uint32_t offsets[4] = { 0 }; + uint32_t pixelFormat = gbmFormatToDrmFormat(gbm_bo_get_format(bo)); QScopedPointer<FrameBuffer> fb(new FrameBuffer); + qCDebug(qLcEglfsKmsDebug, "Adding FB, size %ux%u, DRM format 0x%x", width, height, pixelFormat); - int ret = drmModeAddFB(device()->fd(), width, height, 24, 32, - stride, handle, &fb->fb); + int ret = drmModeAddFB2(device()->fd(), width, height, pixelFormat, + handles, strides, offsets, &fb->fb, 0); if (ret) { qWarning("Failed to create KMS FB!"); - return Q_NULLPTR; + return nullptr; } gbm_bo_set_user_data(bo, fb.data(), bufferDestroyedHandler); return fb.take(); } -QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output) - : QEglFSKmsScreen(device, output) - , m_gbm_surface(Q_NULLPTR) - , m_gbm_bo_current(Q_NULLPTR) - , m_gbm_bo_next(Q_NULLPTR) - , m_cursor(Q_NULLPTR) +QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output, bool headless) + : QEglFSKmsScreen(device, output, headless) + , m_gbm_surface(nullptr) + , m_gbm_bo_current(nullptr) + , m_gbm_bo_next(nullptr) + , m_flipPending(false) + , m_cursor(nullptr) + , m_cloneSource(nullptr) { } @@ -114,6 +131,8 @@ QEglFSKmsGbmScreen::~QEglFSKmsGbmScreen() QPlatformCursor *QEglFSKmsGbmScreen::cursor() const { QKmsScreenConfig *config = device()->screenConfig(); + if (config->headless()) + return nullptr; if (config->hwCursor()) { if (!config->separateScreens()) return static_cast<QEglFSKmsGbmDevice *>(device())->globalCursor(); @@ -132,47 +151,112 @@ QPlatformCursor *QEglFSKmsGbmScreen::cursor() const gbm_surface *QEglFSKmsGbmScreen::createSurface() { if (!m_gbm_surface) { - qCDebug(qLcEglfsKmsDebug) << "Creating window for screen" << name(); + uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format); + qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s with format 0x%x", qPrintable(name()), gbmFormat); m_gbm_surface = gbm_surface_create(static_cast<QEglFSKmsGbmDevice *>(device())->gbmDevice(), rawGeometry().width(), rawGeometry().height(), - GBM_FORMAT_XRGB8888, + gbmFormat, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); } - return m_gbm_surface; + return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface() } -void QEglFSKmsGbmScreen::destroySurface() +void QEglFSKmsGbmScreen::resetSurface() { - if (m_gbm_bo_current) { - gbm_bo_destroy(m_gbm_bo_current); - m_gbm_bo_current = Q_NULLPTR; - } + m_gbm_surface = nullptr; +} - if (m_gbm_bo_next) { - gbm_bo_destroy(m_gbm_bo_next); - m_gbm_bo_next = Q_NULLPTR; +void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones, + const QVector<QPlatformScreen *> &screensCloningThisScreen) +{ + // clone destinations need to know the clone source + const bool clonesAnother = screenThisScreenClones != nullptr; + if (clonesAnother && !screensCloningThisScreen.isEmpty()) { + qWarning("QEglFSKmsGbmScreen %s cannot be clone source and destination at the same time", qPrintable(name())); + return; } + if (clonesAnother) + m_cloneSource = static_cast<QEglFSKmsGbmScreen *>(screenThisScreenClones); + + // clone sources need to know their additional destinations + for (QPlatformScreen *s : screensCloningThisScreen) { + CloneDestination d; + d.screen = static_cast<QEglFSKmsGbmScreen *>(s); + m_cloneDests.append(d); + } +} + +void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb) +{ + QKmsOutput &op(output()); + const int fd = device()->fd(); + + if (!op.mode_set) { + op.mode_set = true; + + bool doModeSet = true; + drmModeCrtcPtr currentMode = drmModeGetCrtc(fd, op.crtc_id); + const bool alreadySet = currentMode && !memcmp(¤tMode->mode, &op.modes[op.mode], sizeof(drmModeModeInfo)); + if (currentMode) + drmModeFreeCrtc(currentMode); + if (alreadySet) { + static bool alwaysDoSet = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ALWAYS_SET_MODE"); + if (!alwaysDoSet) { + qCDebug(qLcEglfsKmsDebug, "Mode already set, skipping modesetting for screen %s", qPrintable(name())); + doModeSet = false; + } + } - if (m_gbm_surface) { - gbm_surface_destroy(m_gbm_surface); - m_gbm_surface = Q_NULLPTR; + if (doModeSet) { + qCDebug(qLcEglfsKmsDebug, "Setting mode for screen %s", qPrintable(name())); + int ret = drmModeSetCrtc(fd, + op.crtc_id, + fb, + 0, 0, + &op.connector_id, 1, + &op.modes[op.mode]); + + if (ret == 0) + setPowerState(PowerStateOn); + else + qErrnoWarning(errno, "Could not set DRM mode for screen %s", qPrintable(name())); + } } } void QEglFSKmsGbmScreen::waitForFlip() { + if (m_headless || m_cloneSource) + return; + // Don't lock the mutex unless we actually need to if (!m_gbm_bo_next) return; QMutexLocker lock(&m_waitForFlipMutex); - while (m_gbm_bo_next) - static_cast<QEglFSKmsGbmDevice *>(device())->handleDrmEvent(); + while (m_gbm_bo_next) { + drmEventContext drmEvent; + memset(&drmEvent, 0, sizeof(drmEvent)); + drmEvent.version = 2; + drmEvent.vblank_handler = nullptr; + drmEvent.page_flip_handler = pageFlipHandler; + drmHandleEvent(device()->fd(), &drmEvent); + } } void QEglFSKmsGbmScreen::flip() { + // For headless screen just return silently. It is not necessarily an error + // to end up here, so show no warnings. + if (m_headless) + return; + + if (m_cloneSource) { + qWarning("Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name())); + return; + } + if (!m_gbm_surface) { qWarning("Cannot sync before platform init!"); return; @@ -185,60 +269,92 @@ void QEglFSKmsGbmScreen::flip() } FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next); + ensureModeSet(fb->fb); QKmsOutput &op(output()); const int fd = device()->fd(); - const uint32_t w = op.modes[op.mode].hdisplay; - const uint32_t h = op.modes[op.mode].vdisplay; - - if (!op.mode_set) { - int ret = drmModeSetCrtc(fd, - op.crtc_id, - fb->fb, - 0, 0, - &op.connector_id, 1, - &op.modes[op.mode]); - - if (ret == -1) { - qErrnoWarning(errno, "Could not set DRM mode!"); - } else { - op.mode_set = true; - setPowerState(PowerStateOn); - - if (!op.plane_set) { - op.plane_set = true; - if (op.wants_plane) { - int ret = drmModeSetPlane(fd, op.plane_id, op.crtc_id, - uint32_t(-1), 0, - 0, 0, w, h, - 0 << 16, 0 << 16, w << 16, h << 16); - if (ret == -1) - qErrnoWarning(errno, "drmModeSetPlane failed"); - } - } - } - } - + m_flipPending = true; int ret = drmModePageFlip(fd, op.crtc_id, fb->fb, DRM_MODE_PAGE_FLIP_EVENT, this); if (ret) { - qErrnoWarning("Could not queue DRM page flip!"); + qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name())); + m_flipPending = false; gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next); - m_gbm_bo_next = Q_NULLPTR; + m_gbm_bo_next = nullptr; + return; + } + + for (CloneDestination &d : m_cloneDests) { + if (d.screen != this) { + d.screen->ensureModeSet(fb->fb); + d.cloneFlipPending = true; + int ret = drmModePageFlip(fd, + d.screen->output().crtc_id, + fb->fb, + DRM_MODE_PAGE_FLIP_EVENT, + d.screen); + if (ret) { + qErrnoWarning("Could not queue DRM page flip for clone screen %s", qPrintable(name())); + d.cloneFlipPending = false; + } + } } } +void QEglFSKmsGbmScreen::pageFlipHandler(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *user_data) +{ + Q_UNUSED(fd); + Q_UNUSED(sequence); + Q_UNUSED(tv_sec); + Q_UNUSED(tv_usec); + + QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(user_data); + screen->flipFinished(); +} + void QEglFSKmsGbmScreen::flipFinished() { + if (m_cloneSource) { + m_cloneSource->cloneDestFlipFinished(this); + return; + } + + m_flipPending = false; + updateFlipStatus(); +} + +void QEglFSKmsGbmScreen::cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen) +{ + for (CloneDestination &d : m_cloneDests) { + if (d.screen == cloneDestScreen) { + d.cloneFlipPending = false; + break; + } + } + updateFlipStatus(); +} + +void QEglFSKmsGbmScreen::updateFlipStatus() +{ + Q_ASSERT(!m_cloneSource); + + if (m_flipPending) + return; + + for (const CloneDestination &d : m_cloneDests) { + if (d.cloneFlipPending) + return; + } + if (m_gbm_bo_current) gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_current); m_gbm_bo_current = m_gbm_bo_next; - m_gbm_bo_next = Q_NULLPTR; + m_gbm_bo_next = nullptr; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h index 341cc95bbe..f5a2122723 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h @@ -54,34 +54,54 @@ class QEglFSKmsGbmCursor; class QEglFSKmsGbmScreen : public QEglFSKmsScreen { public: - QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output); + QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output, bool headless); ~QEglFSKmsGbmScreen(); QPlatformCursor *cursor() const override; - gbm_surface *surface() const { return m_gbm_surface; } gbm_surface *createSurface(); - void destroySurface(); + void resetSurface(); + + void initCloning(QPlatformScreen *screenThisScreenClones, + const QVector<QPlatformScreen *> &screensCloningThisScreen); void waitForFlip() override; - void flip() override; - void flipFinished() override; + + void flip(); private: + void flipFinished(); + void ensureModeSet(uint32_t fb); + void cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen); + void updateFlipStatus(); + + static void pageFlipHandler(int fd, + unsigned int sequence, + unsigned int tv_sec, + unsigned int tv_usec, + void *user_data); + gbm_surface *m_gbm_surface; gbm_bo *m_gbm_bo_current; gbm_bo *m_gbm_bo_next; + bool m_flipPending; QScopedPointer<QEglFSKmsGbmCursor> m_cursor; struct FrameBuffer { - FrameBuffer() : fb(0) {} - uint32_t fb; + uint32_t fb = 0; }; static void bufferDestroyedHandler(gbm_bo *bo, void *data); FrameBuffer *framebufferForBufferObject(gbm_bo *bo); + QEglFSKmsGbmScreen *m_cloneSource; + struct CloneDestination { + QEglFSKmsGbmScreen *screen = nullptr; + bool cloneFlipPending = false; + }; + QVector<CloneDestination> m_cloneDests; + static QMutex m_waitForFlipMutex; }; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp new file mode 100644 index 0000000000..110a592dec --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 "qeglfskmsgbmwindow.h" +#include "qeglfskmsgbmintegration.h" +#include "qeglfskmsgbmscreen.h" + +#include <QtEglSupport/private/qeglconvenience_p.h> + +QT_BEGIN_NAMESPACE + +void QEglFSKmsGbmWindow::resetSurface() +{ + QEglFSKmsGbmScreen *gbmScreen = static_cast<QEglFSKmsGbmScreen *>(screen()); + 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); + // One fullscreen window per screen -> the native window is simply the gbm_surface the screen created. + 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); + } +} + +void QEglFSKmsGbmWindow::invalidateSurface() +{ + QEglFSKmsGbmScreen *gbmScreen = static_cast<QEglFSKmsGbmScreen *>(screen()); + QEglFSWindow::invalidateSurface(); + gbmScreen->resetSurface(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/accessible/comutils.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.h index b1e6183a0f..a19cf7e8bc 100644 --- a/src/plugins/platforms/windows/accessible/comutils.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmwindow.h @@ -1,6 +1,8 @@ /**************************************************************************** ** +** Copyright (C) 2015 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/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -36,29 +38,30 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#ifndef COMUTILS_H -#define COMUTILS_H -#if !defined(_WINDOWS_) && !defined(_WINDOWS_H) && !defined(__WINDOWS__) -#error Must include windows.h first! -#endif +#ifndef QEGLFSKMSGBMWINDOW_H +#define QEGLFSKMSGBMWINDOW_H -#include <ocidl.h> -#include <QtCore/qstring.h> +#include "private/qeglfswindow_p.h" QT_BEGIN_NAMESPACE -class QVariant; +class QEglFSKmsGbmIntegration; -// Originally QVariantToVARIANT copied from ActiveQt - renamed to avoid conflicts in static builds. -bool QVariant2VARIANT(const QVariant &var, VARIANT &arg, const QByteArray &typeName, bool out); - -inline BSTR QStringToBSTR(const QString &str) +class QEglFSKmsGbmWindow : public QEglFSWindow { - return SysAllocStringLen(reinterpret_cast<const OLECHAR *>(str.unicode()), UINT(str.length())); -} +public: + QEglFSKmsGbmWindow(QWindow *w, const QEglFSKmsGbmIntegration *integration) + : QEglFSWindow(w), + m_integration(integration) + { } + void resetSurface() override; + void invalidateSurface() override; -QT_END_NAMESPACE +private: + const QEglFSKmsGbmIntegration *m_integration; +}; -#endif // COMUTILS_H +QT_END_NAMESPACE +#endif // QEGLFSKMSGBMWINDOW_H 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..8c8e6260f1 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevice.cpp @@ -58,7 +58,7 @@ bool QEglFSKmsEglDevice::open() { Q_ASSERT(fd() == -1); - int fd = drmOpen(devicePath().toLocal8Bit().constData(), Q_NULLPTR); + int fd = drmOpen(devicePath().toLocal8Bit().constData(), nullptr); if (Q_UNLIKELY(fd < 0)) qFatal("Could not open DRM (NV) device"); @@ -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_egldevice/qeglfskmsegldeviceintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp index 3af21d768e..a67457a6a5 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp @@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE QEglFSKmsEglDeviceIntegration::QEglFSKmsEglDeviceIntegration() : m_egl_device(EGL_NO_DEVICE_EXT) - , m_funcs(Q_NULLPTR) + , m_funcs(nullptr) { qCDebug(qLcEglfsKmsDebug, "New DRM/KMS on EGLDevice integration created"); } @@ -75,7 +75,7 @@ EGLDisplay QEglFSKmsEglDeviceIntegration::createDisplay(EGLNativeDisplayType nat EGLDisplay display; if (m_funcs->has_egl_platform_device) { - display = m_funcs->get_platform_display(EGL_PLATFORM_DEVICE_EXT, nativeDisplay, Q_NULLPTR); + display = m_funcs->get_platform_display(EGL_PLATFORM_DEVICE_EXT, nativeDisplay, nullptr); } else { qWarning("EGL_EXT_platform_device not available, falling back to legacy path!"); display = eglGetDisplay(nativeDisplay); @@ -162,7 +162,7 @@ void QEglFSKmsEglDeviceWindow::resetSurface() qCDebug(qLcEglfsKmsDebug, "Could not query number of EGLStream FIFO frames"); } - if (!m_integration->m_funcs->get_output_layers(display, Q_NULLPTR, Q_NULLPTR, 0, &count) || count == 0) { + if (!m_integration->m_funcs->get_output_layers(display, nullptr, nullptr, 0, &count) || count == 0) { qWarning("No output layers found"); return; } @@ -172,7 +172,7 @@ void QEglFSKmsEglDeviceWindow::resetSurface() QVector<EGLOutputLayerEXT> layers; layers.resize(count); EGLint actualCount; - if (!m_integration->m_funcs->get_output_layers(display, Q_NULLPTR, layers.data(), count, &actualCount)) { + if (!m_integration->m_funcs->get_output_layers(display, nullptr, layers.data(), count, &actualCount)) { qWarning("Failed to get layers"); return; } @@ -180,7 +180,7 @@ void QEglFSKmsEglDeviceWindow::resetSurface() QEglFSKmsEglDeviceScreen *cur_screen = static_cast<QEglFSKmsEglDeviceScreen *>(screen()); Q_ASSERT(cur_screen); QKmsOutput &output(cur_screen->output()); - const uint32_t wantedId = !output.wants_plane ? output.crtc_id : output.plane_id; + const uint32_t wantedId = !output.wants_forced_plane ? output.crtc_id : output.forced_plane_id; qCDebug(qLcEglfsKmsDebug, "Searching for id: %d", wantedId); EGLOutputLayerEXT layer = EGL_NO_OUTPUT_LAYER_EXT; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp index a27c89faab..531b73d1dc 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp @@ -105,12 +105,12 @@ void QEglFSKmsEglDeviceScreen::waitForFlip() qErrnoWarning(errno, "drmModeSetCrtc failed"); } - if (!op.plane_set) { - op.plane_set = true; + if (!op.forced_plane_set) { + op.forced_plane_set = true; - if (op.wants_plane) { - qCDebug(qLcEglfsKmsDebug, "Setting plane %u", op.plane_id); - int ret = drmModeSetPlane(fd, op.plane_id, op.crtc_id, uint32_t(-1), 0, + if (op.wants_forced_plane) { + qCDebug(qLcEglfsKmsDebug, "Setting plane %u", op.forced_plane_id); + int ret = drmModeSetPlane(fd, op.forced_plane_id, op.crtc_id, uint32_t(-1), 0, 0, 0, w, h, 0 << 16, 0 << 16, w << 16, h << 16); if (ret == -1) 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..4d1321079c 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 @@ -19,4 +19,5 @@ SOURCES += $$PWD/qeglfskmsintegration.cpp \ HEADERS += $$PWD/qeglfskmsintegration.h \ $$PWD/qeglfskmsdevice.h \ - $$PWD/qeglfskmsscreen.h + $$PWD/qeglfskmsscreen.h \ + $$PWD/qeglfskmshelpers.h diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmshelpers.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmshelpers.h new file mode 100644 index 0000000000..c9e5f04a7b --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmshelpers.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 QEGLFSKMSHELPERS_H +#define QEGLFSKMSHELPERS_H + +#include <QString> + +QT_BEGIN_NAMESPACE + +inline QString q_fourccToString(uint code) +{ + QString s; + s.reserve(4); + s.append(code & 0xff); + s.append((code >> 8) & 0xff); + s.append((code >> 16) & 0xff); + s.append((code >> 24) & 0xff); + return s; +} + +QT_END_NAMESPACE + +#endif // QEGLFSKMSHELPERS_H diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.cpp index c77151181e..06bc272050 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.cpp @@ -55,7 +55,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcEglfsKmsDebug, "qt.qpa.eglfs.kms") QEglFSKmsIntegration::QEglFSKmsIntegration() - : m_device(Q_NULLPTR), + : m_device(nullptr), m_screenConfig(new QKmsScreenConfig) { } @@ -78,7 +78,7 @@ void QEglFSKmsIntegration::platformDestroy() qCDebug(qLcEglfsKmsDebug, "platformDestroy: Closing DRM device"); m_device->close(); delete m_device; - m_device = Q_NULLPTR; + m_device = nullptr; } EGLNativeDisplayType QEglFSKmsIntegration::platformDisplay() const @@ -133,6 +133,24 @@ bool QEglFSKmsIntegration::supportsPBuffers() const return m_screenConfig->supportsPBuffers(); } +void *QEglFSKmsIntegration::nativeResourceForIntegration(const QByteArray &name) +{ + if (name == QByteArrayLiteral("dri_fd") && m_device) + return (void *) (qintptr) m_device->fd(); + + return nullptr; +} + +void *QEglFSKmsIntegration::nativeResourceForScreen(const QByteArray &resource, QScreen *screen) +{ + QEglFSKmsScreen *s = static_cast<QEglFSKmsScreen *>(screen->handle()); + if (s) { + if (resource == QByteArrayLiteral("dri_crtcid")) + return (void *) (qintptr) s->output().crtc_id; + } + return nullptr; +} + QKmsDevice *QEglFSKmsIntegration::device() const { return m_device; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.h index 9955616919..e2c37f60fc 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration.h @@ -69,6 +69,8 @@ public: bool hasCapability(QPlatformIntegration::Capability cap) const override; void waitForVSync(QPlatformSurface *surface) const override; bool supportsPBuffers() const override; + void *nativeResourceForIntegration(const QByteArray &name) override; + void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override; QKmsDevice *device() const; QKmsScreenConfig *screenConfig() const; 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..5e45b42abe 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2017 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ** Copyright (C) 2016 Pelagicore AG ** Contact: https://www.qt.io/licensing/ ** @@ -68,14 +68,31 @@ private: QEglFSKmsScreen *m_screen; }; -QEglFSKmsScreen::QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output) - : QEglFSScreen(eglGetDisplay((EGLNativeDisplayType) device->nativeDisplay())) +QEglFSKmsScreen::QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output, bool headless) + : QEglFSScreen(static_cast<QEglFSIntegration *>(QGuiApplicationPrivate::platformIntegration())->display()) , m_device(device) , m_output(output) , m_powerState(PowerStateOn) , m_interruptHandler(new QEglFSKmsInterruptHandler(this)) + , m_headless(headless) { 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() @@ -93,6 +110,9 @@ void QEglFSKmsScreen::setVirtualPosition(const QPoint &pos) // geometry() calls rawGeometry() and may apply additional transforms. QRect QEglFSKmsScreen::rawGeometry() const { + if (m_headless) + return QRect(QPoint(0, 0), m_device->screenConfig()->headlessSize()); + const int mode = m_output.mode; return QRect(m_pos.x(), m_pos.y(), m_output.modes[mode].hdisplay, @@ -101,12 +121,30 @@ QRect QEglFSKmsScreen::rawGeometry() const int QEglFSKmsScreen::depth() const { - return 32; + return format() == QImage::Format_RGB16 ? 16 : 32; } QImage::Format QEglFSKmsScreen::format() const { - return QImage::Format_RGB32; + // the result can be slightly incorrect, it won't matter in practice + switch (m_output.drm_format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + return QImage::Format_ARGB32; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return QImage::Format_RGB16; + case DRM_FORMAT_XRGB2101010: + return QImage::Format_RGB30; + case DRM_FORMAT_XBGR2101010: + return QImage::Format_BGR30; + case DRM_FORMAT_ARGB2101010: + return QImage::Format_A2RGB30_Premultiplied; + case DRM_FORMAT_ABGR2101010: + return QImage::Format_A2BGR30_Premultiplied; + default: + return QImage::Format_RGB32; + } } QSizeF QEglFSKmsScreen::physicalSize() const @@ -143,22 +181,25 @@ Qt::ScreenOrientation QEglFSKmsScreen::orientation() const QString QEglFSKmsScreen::name() const { - return m_output.name; + return !m_headless ? m_output.name : QStringLiteral("qt_Headless"); } -void QEglFSKmsScreen::destroySurface() +QString QEglFSKmsScreen::manufacturer() const { + return m_edid.manufacturer; } -void QEglFSKmsScreen::waitForFlip() +QString QEglFSKmsScreen::model() const { + return m_edid.model.isEmpty() ? m_edid.identifier : m_edid.model; } -void QEglFSKmsScreen::flip() +QString QEglFSKmsScreen::serialNumber() const { + return m_edid.serialNumber; } -void QEglFSKmsScreen::flipFinished() +void QEglFSKmsScreen::waitForFlip() { } @@ -169,10 +210,35 @@ void QEglFSKmsScreen::restoreMode() qreal QEglFSKmsScreen::refreshRate() const { + if (m_headless) + return 60; + quint32 refresh = m_output.modes[m_output.mode].vrefresh; 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..7f395aacb7 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 @@ -55,7 +56,7 @@ class QEglFSKmsInterruptHandler; class Q_EGLFS_EXPORT QEglFSKmsScreen : public QEglFSScreen { public: - QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output); + QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output, bool headless = false); ~QEglFSKmsScreen(); void setVirtualPosition(const QPoint &pos); @@ -72,18 +73,23 @@ 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; } - QKmsDevice *device() const { return m_device; } + QVector<QPlatformScreen::Mode> modes() const override; - void destroySurface(); + int currentMode() const override; + int preferredMode() const override; + + QKmsDevice *device() const { return m_device; } virtual void waitForFlip(); - virtual void flip(); - virtual void flipFinished(); QKmsOutput &output() { return m_output; } void restoreMode(); @@ -97,6 +103,7 @@ protected: QKmsDevice *m_device; QKmsOutput m_output; + QEdidParser m_edid; QPoint m_pos; QList<QPlatformScreen *> m_siblings; @@ -104,6 +111,8 @@ protected: PowerState m_powerState; QEglFSKmsInterruptHandler *m_interruptHandler; + + bool m_headless; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/eglfs_kms_vsp2.json b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/eglfs_kms_vsp2.json new file mode 100644 index 0000000000..b898dc27cb --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/eglfs_kms_vsp2.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "eglfs_kms_vsp2" ] +} diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/eglfs_kms_vsp2.pro b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/eglfs_kms_vsp2.pro new file mode 100644 index 0000000000..826c2f989f --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/eglfs_kms_vsp2.pro @@ -0,0 +1,31 @@ +TARGET = qeglfs-kms-vsp2-integration + +PLUGIN_TYPE = egldeviceintegrations +PLUGIN_CLASS_NAME = QEglFSKmsVsp2IntegrationPlugin +load(qt_plugin) + +QT += core-private gui-private eglfsdeviceintegration-private eglfs_kms_support-private kms_support-private edid_support-private + +INCLUDEPATH += $$PWD/../../api $$PWD/../eglfs_kms_support + +# Avoid X11 header collision, use generic EGL native types +DEFINES += QT_EGL_NO_X11 + +QMAKE_USE += gbm drm v4l2 +CONFIG += egl +QMAKE_LFLAGS += $$QMAKE_LFLAGS_NOUNDEF + +SOURCES += $$PWD/qeglfskmsvsp2main.cpp \ + $$PWD/qeglfskmsvsp2integration.cpp \ + $$PWD/qeglfskmsvsp2device.cpp \ + $$PWD/qeglfskmsvsp2screen.cpp \ + $$PWD/qlinuxmediadevice.cpp \ + $$PWD/qvsp2blendingdevice.cpp + +HEADERS += $$PWD/qeglfskmsvsp2integration.h \ + $$PWD/qeglfskmsvsp2device.h \ + $$PWD/qeglfskmsvsp2screen.h \ + $$PWD/qlinuxmediadevice.h \ + $$PWD/qvsp2blendingdevice.h + +OTHER_FILES += $$PWD/eglfs_kms.json diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2device.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2device.cpp new file mode 100644 index 0000000000..44f855910c --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2device.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 Pelagicore AG +** 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 "qeglfskmsvsp2device.h" +#include "qeglfskmsvsp2screen.h" + +#include "qeglfsintegration_p.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/private/qcore_unix_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) + +QEglFSKmsVsp2Device::QEglFSKmsVsp2Device(QKmsScreenConfig *screenConfig, const QString &path) + : QEglFSKmsDevice(screenConfig, path) +{ +} + +bool QEglFSKmsVsp2Device::open() +{ + Q_ASSERT(fd() == -1); + Q_ASSERT(m_gbm_device == nullptr); + + int fd = qt_safe_open(devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC); + if (fd == -1) { + qErrnoWarning("Could not open DRM device %s", qPrintable(devicePath())); + return false; + } + + qCDebug(qLcEglfsKmsDebug) << "Creating GBM device for file descriptor" << fd + << "obtained from" << devicePath(); + m_gbm_device = gbm_create_device(fd); + if (!m_gbm_device) { + qErrnoWarning("Could not create GBM device"); + qt_safe_close(fd); + fd = -1; + return false; + } + + setFd(fd); + + return true; +} + +void QEglFSKmsVsp2Device::close() +{ + // Note: screens are gone at this stage. + + if (m_gbm_device) { + gbm_device_destroy(m_gbm_device); + m_gbm_device = nullptr; + } + + if (fd() != -1) { + qt_safe_close(fd()); + setFd(-1); + } +} + +void *QEglFSKmsVsp2Device::nativeDisplay() const +{ + return m_gbm_device; +} + +gbm_device * QEglFSKmsVsp2Device::gbmDevice() const +{ + return m_gbm_device; +} + +QPlatformScreen *QEglFSKmsVsp2Device::createScreen(const QKmsOutput &output) +{ + auto *screen = new QEglFSKmsVsp2Screen(this, output); + + return screen; +} + +QPlatformScreen *QEglFSKmsVsp2Device::createHeadlessScreen() +{ + qWarning() << Q_FUNC_INFO << "Not implemented yet"; + return nullptr; +} + +void QEglFSKmsVsp2Device::registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector<QPlatformScreen *> &screensCloningThisScreen) +{ + Q_UNUSED(screen); + qWarning() << Q_FUNC_INFO << "Not implemented yet"; + if (!screenThisScreenClones && screensCloningThisScreen.isEmpty()) + return; + +// auto *vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen); +// vsp2Screen->initCloning(screenThisScreenClones, screensCloningThisScreen); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2device.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2device.h new file mode 100644 index 0000000000..c795fa4005 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2device.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 Pelagicore AG +** 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 QEGLFSKMSVSP2DEVICE_H +#define QEGLFSKMSVSP2DEVICE_H + +#include <qeglfskmsdevice.h> + +#include <gbm.h> + +QT_BEGIN_NAMESPACE + +class QEglFSKmsScreen; + +class QEglFSKmsVsp2Device: public QEglFSKmsDevice +{ +public: + QEglFSKmsVsp2Device(QKmsScreenConfig *screenConfig, const QString &path); + + bool open() override; + void close() override; + + void *nativeDisplay() const override; + gbm_device *gbmDevice() const; + + QPlatformScreen *createScreen(const QKmsOutput &output) override; + QPlatformScreen *createHeadlessScreen() override; + void registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector<QPlatformScreen *> &screensCloningThisScreen) override; + +private: + Q_DISABLE_COPY(QEglFSKmsVsp2Device) + + gbm_device *m_gbm_device = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QEGLFSKMSVSP2DEVICE_H diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2integration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2integration.cpp new file mode 100644 index 0000000000..0d9b6b6290 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2integration.cpp @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 Pelagicore AG +** 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 "qeglfskmsvsp2integration.h" +#include "qeglfskmsvsp2device.h" +#include "qeglfskmsvsp2screen.h" +#include "private/qeglfswindow_p.h" + +#include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h> +#include <QtEglSupport/private/qeglconvenience_p.h> +#include <QtCore/QLoggingCategory> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtGui/qpa/qplatformwindow.h> +#include <QtGui/QScreen> +#include <QtPlatformHeaders/qeglfsfunctions.h> + +#include <xf86drm.h> +#include <xf86drmMode.h> +#include <gbm.h> + +QT_BEGIN_NAMESPACE + +QEglFSKmsVsp2Integration::QEglFSKmsVsp2Integration() +{ + qCDebug(qLcEglfsKmsDebug, "New DRM/KMS via Vsp2 integration created"); +} + +#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 QEglFSKmsVsp2Integration::createDisplay(EGLNativeDisplayType nativeDisplay) +{ + 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")); + } + + 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 display; +} + +EGLNativeWindowType QEglFSKmsVsp2Integration::createNativeOffscreenWindow(const QSurfaceFormat &format) +{ + Q_UNUSED(format); + Q_ASSERT(device()); + + gbm_surface *surface = gbm_surface_create(static_cast<QEglFSKmsVsp2Device *>(device())->gbmDevice(), + 1, 1, + GBM_FORMAT_XRGB8888, + GBM_BO_USE_RENDERING); + + return reinterpret_cast<EGLNativeWindowType>(surface); +} + +void QEglFSKmsVsp2Integration::destroyNativeWindow(EGLNativeWindowType window) +{ + gbm_surface *surface = reinterpret_cast<gbm_surface *>(window); + //TODO call screen destroysurface instead + gbm_surface_destroy(surface); +} + +void QEglFSKmsVsp2Integration::presentBuffer(QPlatformSurface *surface) +{ + QWindow *window = static_cast<QWindow *>(surface->surface()); + auto *screen = static_cast<QEglFSKmsVsp2Screen *>(window->screen()->handle()); + screen->flip(); +} + +QFunctionPointer QEglFSKmsVsp2Integration::platformFunction(const QByteArray &function) const +{ + if (function == QEglFSFunctions::vsp2AddLayerTypeIdentifier()) + return QFunctionPointer(addLayerStatic); + if (function == QEglFSFunctions::vsp2RemoveLayerTypeIdentifier()) + return QFunctionPointer(removeLayerStatic); + if (function == QEglFSFunctions::vsp2SetLayerBufferTypeIdentifier()) + return QFunctionPointer(setLayerBufferStatic); + if (function == QEglFSFunctions::vsp2SetLayerPositionTypeIdentifier()) + return QFunctionPointer(setLayerPositionStatic); + if (function == QEglFSFunctions::vsp2SetLayerAlphaTypeIdentifier()) + return QFunctionPointer(setLayerAlphaStatic); + if (function == QEglFSFunctions::vsp2AddBlendListenerTypeIdentifier()) + return QFunctionPointer(addBlendListenerStatic); + + return nullptr; +} + +QKmsDevice *QEglFSKmsVsp2Integration::createDevice() +{ + QString path = screenConfig()->devicePath(); + if (!path.isEmpty()) { + qCDebug(qLcEglfsKmsDebug) << "VSP2: Using DRM device" << path << "specified in config file"; + } else { + QDeviceDiscovery *d = QDeviceDiscovery::create(QDeviceDiscovery::Device_VideoMask); + const QStringList devices = d->scanConnectedDevices(); + qCDebug(qLcEglfsKmsDebug) << "Found the following video devices:" << devices; + d->deleteLater(); + + if (Q_UNLIKELY(devices.isEmpty())) + qFatal("Could not find DRM device!"); + + path = devices.first(); + qCDebug(qLcEglfsKmsDebug) << "Using" << path; + } + + return new QEglFSKmsVsp2Device(screenConfig(), path); +} + +int QEglFSKmsVsp2Integration::addLayerStatic(const QScreen *screen, int dmabufFd, const QSize &size, const QPoint &position, uint pixelFormat, uint bytesPerLine) +{ + auto vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen->handle()); + return vsp2Screen->addLayer(dmabufFd, size, position, pixelFormat, bytesPerLine); +} + +bool QEglFSKmsVsp2Integration::removeLayerStatic(const QScreen *screen, int id) +{ + auto vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen->handle()); + return vsp2Screen->removeLayer(id); +} + +void QEglFSKmsVsp2Integration::setLayerBufferStatic(const QScreen *screen, int id, int dmabufFd) +{ + auto vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen->handle()); + vsp2Screen->setLayerBuffer(id, dmabufFd); +} + +void QEglFSKmsVsp2Integration::setLayerPositionStatic(const QScreen *screen, int id, const QPoint &position) +{ + auto vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen->handle()); + vsp2Screen->setLayerPosition(id, position); +} + +void QEglFSKmsVsp2Integration::setLayerAlphaStatic(const QScreen *screen, int id, qreal alpha) +{ + auto vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen->handle()); + vsp2Screen->setLayerAlpha(id, alpha); +} + +void QEglFSKmsVsp2Integration::addBlendListenerStatic(const QScreen *screen, void(*callback)()) +{ + auto vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen->handle()); + vsp2Screen->addBlendListener(callback); +} + +class QEglFSKmsVsp2Window : public QEglFSWindow +{ +public: + QEglFSKmsVsp2Window(QWindow *w, const QEglFSKmsVsp2Integration *integration) + : QEglFSWindow(w) + , m_integration(integration) + {} + void resetSurface() override; + void invalidateSurface() override; + const QEglFSKmsVsp2Integration *m_integration; +}; + +void QEglFSKmsVsp2Window::resetSurface() +{ + auto *vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen()); + EGLDisplay display = vsp2Screen->display(); + QSurfaceFormat platformFormat = m_integration->surfaceFormatFor(window()->requestedFormat()); + m_config = QEglFSDeviceIntegration::chooseConfig(display, platformFormat); + m_format = q_glFormatFromConfig(display, m_config, platformFormat); + // One fullscreen window per screen -> the native window is simply the gbm_surface the screen created. + m_window = reinterpret_cast<EGLNativeWindowType>(vsp2Screen->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); + } +} + +void QEglFSKmsVsp2Window::invalidateSurface() +{ + auto *vsp2Screen = static_cast<QEglFSKmsVsp2Screen *>(screen()); + QEglFSWindow::invalidateSurface(); + vsp2Screen->resetSurface(); +} + +QEglFSWindow *QEglFSKmsVsp2Integration::createWindow(QWindow *window) const +{ + return new QEglFSKmsVsp2Window(window, this); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2integration.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2integration.h new file mode 100644 index 0000000000..b1a8a2edf3 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2integration.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 Pelagicore AG +** 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 QEGLFSKMSVSP2INTEGRATION_H +#define QEGLFSKMSVSP2INTEGRATION_H + +#include "qeglfskmsintegration.h" +#include <QtCore/QMap> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE + +class QEglFSKmsDevice; + +class QEglFSKmsVsp2Integration : public QEglFSKmsIntegration +{ +public: + QEglFSKmsVsp2Integration(); + + EGLDisplay createDisplay(EGLNativeDisplayType nativeDisplay) override; + EGLNativeWindowType createNativeOffscreenWindow(const QSurfaceFormat &format) override; + void destroyNativeWindow(EGLNativeWindowType window) override; + + void presentBuffer(QPlatformSurface *surface) override; + QEglFSWindow *createWindow(QWindow *window) const override; + + QFunctionPointer platformFunction(const QByteArray &function) const override; + +protected: + QKmsDevice *createDevice() override; + +private: + static int addLayerStatic(const QScreen *screen, int dmabufFd, const QSize &size, const QPoint &position, uint pixelFormat, uint bytesPerLine); + static bool removeLayerStatic(const QScreen *screen, int id); + static void setLayerBufferStatic(const QScreen *screen, int id, int dmabufFd); + static void setLayerPositionStatic(const QScreen *screen, int id, const QPoint &position); + static void setLayerAlphaStatic(const QScreen *screen, int id, qreal alpha); + static void addBlendListenerStatic(const QScreen *screen, void(*callback)()); +}; + +QT_END_NAMESPACE + +#endif // QEGLFSKMSVSP2INTEGRATION_H diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2main.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2main.cpp new file mode 100644 index 0000000000..1345d38359 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2main.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the qmake spec 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 "private/qeglfsdeviceintegration_p.h" +#include "qeglfskmsvsp2integration.h" + +QT_BEGIN_NAMESPACE + +class QEglFSKmsVsp2IntegrationPlugin : public QEglFSDeviceIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QEglFSDeviceIntegrationFactoryInterface_iid FILE "eglfs_kms_vsp2.json") + +public: + QEglFSDeviceIntegration *create() override { return new QEglFSKmsVsp2Integration; } +}; + +QT_END_NAMESPACE + +#include "qeglfskmsvsp2main.moc" diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp new file mode 100644 index 0000000000..88b401c920 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2016 Pelagicore AG +** 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 "qeglfskmsvsp2screen.h" +#include "qeglfskmsvsp2device.h" +#include <qeglfskmshelpers.h> + +#include <QtCore/QLoggingCategory> +#include <QtGui/private/qguiapplication_p.h> + +#include <drm_fourcc.h> +#include <xf86drm.h> +#include <fcntl.h> + +//TODO move to qlinuxmediadevice? +#include <cstdlib> //this needs to go before mediactl/mediactl.h because it uses size_t without including it +extern "C" { +#include <mediactl/mediactl.h> +#include <mediactl/v4l2subdev.h> //needed in header for default arguments +} + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) + +static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) +{ + Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); + return drmFormat; +} + +static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat) //TODO: is this needed? +{ + Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); + return gbmFormat; +} + +void QEglFSKmsVsp2Screen::dmaBufferDestroyedHandler(gbm_bo *gbmBo, void *data) +{ + Q_UNUSED(gbmBo); //TODO: do we need to do something with it after all? + auto dmabuf = static_cast<DmaBuffer *>(data); + //TODO: need some extra cleanup here? + delete dmabuf; +} + +QEglFSKmsVsp2Screen::DmaBuffer *QEglFSKmsVsp2Screen::dmaBufferForGbmBuffer(gbm_bo *gbmBo) +{ + auto existingBuffer = static_cast<DmaBuffer *>(gbm_bo_get_user_data(gbmBo)); + if (existingBuffer) + return existingBuffer; + + uint32_t handle = gbm_bo_get_handle(gbmBo).u32; + QScopedPointer<DmaBuffer> fb(new DmaBuffer); + int ret = drmPrimeHandleToFD(device()->fd(), handle, DRM_CLOEXEC, &fb->dmabufFd); + if (ret) { + qWarning("Failed to create dmabuf file descriptor for buffer with drmPrimeHandleToFd"); + return nullptr; + } + + gbm_bo_set_user_data(gbmBo, fb.data(), dmaBufferDestroyedHandler); + return fb.take(); +} + +QEglFSKmsVsp2Screen::QEglFSKmsVsp2Screen(QKmsDevice *device, const QKmsOutput &output) + : QEglFSKmsScreen(device, output) + , m_blender(new Blender(this)) +{ +} + +gbm_surface *QEglFSKmsVsp2Screen::createSurface() +{ + if (!m_gbmSurface) { + uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format); + qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s with format 0x%x", qPrintable(name()), gbmFormat); + Q_ASSERT(rawGeometry().isValid()); + m_gbmSurface = gbm_surface_create(static_cast<QEglFSKmsVsp2Device *>(device())->gbmDevice(), + uint(rawGeometry().width()), + uint(rawGeometry().height()), + gbmFormat, + GBM_BO_USE_RENDERING); + } + + if (!m_blendDevice) + initVsp2(); + + if (m_frameBuffers[m_backFb].dmabufFd == -1) + initDumbFrameBuffers(); + + return m_gbmSurface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() +} + +void QEglFSKmsVsp2Screen::resetSurface() +{ + m_gbmSurface = nullptr; +} + +void QEglFSKmsVsp2Screen::initDumbFrameBuffers() +{ + for (auto &fb : m_frameBuffers) + initDumbFrameBuffer(fb); +} + +void QEglFSKmsVsp2Screen::initVsp2() +{ + qCDebug(qLcEglfsKmsDebug, "Initializing Vsp2 hardware"); + const QSize screenSize = rawGeometry().size(); + m_blendDevice.reset(new QVsp2BlendingDevice(screenSize)); + + // Enable input for main buffer drawn by the compositor (always on) + const uint bytesPerLine = uint(screenSize.width()) * 4; //TODO: is this ok? + bool formatSet = m_blendDevice->enableInput(m_qtLayer, QRect(QPoint(), screenSize), m_output.drm_format, bytesPerLine); + if (!formatSet) { + const uint32_t fallbackFormat = DRM_FORMAT_ARGB8888; + qWarning() << "Failed to set format" << q_fourccToString(m_output.drm_format) + << "falling back to" << q_fourccToString(fallbackFormat); + formatSet = m_blendDevice->enableInput(m_qtLayer, QRect(QPoint(), screenSize), fallbackFormat, bytesPerLine); + if (!formatSet) + qFatal("Failed to set vsp2 blending format"); + } +} + +int QEglFSKmsVsp2Screen::addLayer(int dmabufFd, const QSize &size, const QPoint &position, uint drmPixelFormat, uint bytesPerLine) +{ + int index = m_blendDevice->enableInput(QRect(position, size), drmPixelFormat, bytesPerLine); + if (index != -1) { + m_blendDevice->setInputBuffer(index, dmabufFd); + int id = index; //TODO: maybe make id something independent of layer index? + qCDebug(qLcEglfsKmsDebug) << "Enabled extra layer for vsp input" << index; + return id; + } + qWarning() << "Failed to add layer"; + return -1; +} + +void QEglFSKmsVsp2Screen::setLayerBuffer(int id, int dmabufFd) +{ + int layerIndex = id; + m_blendDevice->setInputBuffer(layerIndex, dmabufFd); + if (!m_blendScheduled) { + m_blendScheduled = true; + QCoreApplication::postEvent(m_blender.data(), new QEvent(QEvent::User)); + } +} + +void QEglFSKmsVsp2Screen::setLayerPosition(int id, const QPoint &position) +{ + int layerIndex = id; + m_blendDevice->setInputPosition(layerIndex, position); +} + +void QEglFSKmsVsp2Screen::setLayerAlpha(int id, qreal alpha) +{ + int layerIndex = id; + m_blendDevice->setInputAlpha(layerIndex, alpha); +} + +bool QEglFSKmsVsp2Screen::removeLayer(int id) +{ + int layerIndex = id; + m_blendDevice->disableInput(layerIndex); + return true; +} + +void QEglFSKmsVsp2Screen::addBlendListener(void (*callback)()) +{ + m_blendFinishedCallbacks.append(callback); +} + +void QEglFSKmsVsp2Screen::flip() +{ + if (!m_gbmSurface) { + qWarning("Cannot sync before platform init!"); + return; + } + + if (!m_blendScheduled && !m_nextGbmBo) { + m_nextGbmBo = gbm_surface_lock_front_buffer(m_gbmSurface); + + if (!m_nextGbmBo) { + qWarning("Could not lock GBM surface front buffer!"); + return; + } + + m_blendScheduled = true; + QCoreApplication::postEvent(m_blender.data(), new QEvent(QEvent::User)); + } +} + +void QEglFSKmsVsp2Screen::ensureModeSet() +{ + const int driFd = device()->fd(); + QKmsOutput &op(output()); + if (!op.mode_set) { + int ret = drmModeSetCrtc(driFd, + op.crtc_id, + m_frameBuffers[m_backFb].drmBufferId, + 0, 0, + &op.connector_id, 1, + &op.modes[op.mode]); + + if (ret == -1) { + qErrnoWarning(errno, "Could not set DRM mode!"); + } else { + op.mode_set = true; + setPowerState(PowerStateOn); + } + } +} + +void QEglFSKmsVsp2Screen::doDrmFlip() +{ + QKmsOutput &op(output()); + const int driFd = device()->fd(); + + int ret = drmModePageFlip(driFd, + op.crtc_id, + m_frameBuffers[m_backFb].drmBufferId, + 0, + this); + + if (ret) + qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name())); + + m_backFb = (m_backFb + 1) % 2; +} + +void QEglFSKmsVsp2Screen::blendAndFlipDrm() +{ + m_blendScheduled = false; + if (!m_nextGbmBo && !m_blendDevice->isDirty()) + return; + + FrameBuffer &backBuffer = m_frameBuffers[m_backFb]; + if (backBuffer.dmabufFd == -1) + initDumbFrameBuffers(); + + if (m_nextGbmBo) { + Q_ASSERT(m_nextGbmBo != m_currentGbmBo); + int compositorBackBufferDmaFd = dmaBufferForGbmBuffer(m_nextGbmBo)->dmabufFd; + m_blendDevice->setInputBuffer(m_qtLayer, compositorBackBufferDmaFd); + + if (m_currentGbmBo) + gbm_surface_release_buffer(m_gbmSurface, m_currentGbmBo); + m_currentGbmBo = m_nextGbmBo; + m_nextGbmBo = nullptr; + } + + ensureModeSet(); + + if (!m_blendDevice) + initVsp2(); + + if (!m_blendDevice->isDirty()) + return; + + const int driFd = device()->fd(); + drmVBlank vBlank; + vBlank.request.type = static_cast<drmVBlankSeqType>(DRM_VBLANK_RELATIVE | DRM_VBLANK_SECONDARY); //TODO: make secondary configurable (or automatic) + vBlank.request.sequence = 1; + vBlank.request.signal = 0; + drmWaitVBlank(driFd, &vBlank); + + if (!m_blendDevice->blend(backBuffer.dmabufFd)) + qWarning() << "Vsp2: blending failed"; + + for (auto cb : m_blendFinishedCallbacks) + cb(); + + doDrmFlip(); +} + +void QEglFSKmsVsp2Screen::initDumbFrameBuffer(FrameBuffer &fb) +{ + QKmsOutput &op(output()); + const uint32_t width = op.modes[op.mode].hdisplay; + const uint32_t height = op.modes[op.mode].vdisplay; + + Q_ASSERT(fb.dmabufFd == -1); + const uint32_t dumbBufferFlags = 0; //TODO: do we want some flags? What's possible? + const uint32_t bpp = 32; + + drm_mode_create_dumb creq = { + height, + width, + bpp, + dumbBufferFlags, + 0, 0, 0 //return values + }; + + const int driFd = device()->fd(); + if (drmIoctl(driFd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) + qFatal("Failed to create dumb buffer: %s", strerror(errno)); + +// uint32_t handles[4] = { gbm_bo_get_handle(bo).u32 }; +// uint32_t strides[4] = { gbm_bo_get_stride(bo) }; +// uint32_t offsets[4] = { 0 }; +// uint32_t pixelFormat = gbmFormatToDrmFormat(gbm_bo_get_format(bo)); + + //TODO: support additional planes + uint32_t gbmBoHandles[4] = { creq.handle, 0, 0, 0 }; + uint32_t strides[4] = { creq.pitch, 0, 0, 0 }; + uint32_t offsets[4] = { 0 }; + uint32_t pixelFormat = DRM_FORMAT_ARGB8888; //TODO: support other formats? + uint32_t drmFlags = 0; + + qCDebug(qLcEglfsKmsDebug) << "Adding FB" << QSize(width, height) + << ", DRM format" << q_fourccToString(pixelFormat); + int ret = drmModeAddFB2(driFd, width, height, pixelFormat, + gbmBoHandles, strides, offsets, &fb.drmBufferId, drmFlags); + if (ret) + qFatal("drmModeAddFB2 failed: %s", strerror(errno)); + + drmPrimeHandleToFD(driFd, gbmBoHandles[0], DRM_CLOEXEC, &fb.dmabufFd); +} + +bool QEglFSKmsVsp2Screen::Blender::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::User: + m_screen->blendAndFlipDrm(); + return true; + default: + return QObject::event(event); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.h new file mode 100644 index 0000000000..fa03e36785 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 Pelagicore AG +** 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 QEGLFSKMSVSP2SCREEN_H +#define QEGLFSKMSVSP2SCREEN_H + +#include "qeglfskmsscreen.h" +#include "qvsp2blendingdevice.h" +#include <QtCore/QMutex> + +#include <gbm.h> + +QT_BEGIN_NAMESPACE + +class QEglFSKmsVsp2Screen : public QEglFSKmsScreen +{ +public: + QEglFSKmsVsp2Screen(QKmsDevice *device, const QKmsOutput &output); + + gbm_surface *createSurface(); + void resetSurface(); + + void initDumbFrameBuffers(); + void initVsp2(); + + //TODO: use a fixed index API instead of auto increment? + int addLayer(int dmabufFd, const QSize &size, const QPoint &position, uint drmPixelFormat, uint bytesPerLine); + void setLayerBuffer(int id, int dmabufFd); + void setLayerPosition(int id, const QPoint &position); + void setLayerAlpha(int id, qreal alpha); + bool removeLayer(int id); + void addBlendListener(void (*callback)()); + + void flip(); + void blendAndFlipDrm(); + +private: + class Blender : public QObject // This is a workaround we really would want the screen to be a QObject + { + public: + Blender(QEglFSKmsVsp2Screen *screen) : m_screen(screen) {} + ~Blender() override {} + bool event(QEvent *event) override; + QEglFSKmsVsp2Screen *m_screen = nullptr; + }; + QScopedArrayPointer<Blender> m_blender; + + gbm_surface *m_gbmSurface = nullptr; + gbm_bo *m_currentGbmBo = nullptr; + gbm_bo *m_nextGbmBo = nullptr; + + QScopedPointer<QVsp2BlendingDevice> m_blendDevice; + + struct FrameBuffer { //these are for buffers that have been blended by the bru + uint32_t drmBufferId = 0; + int dmabufFd = -1; + }; + std::array<FrameBuffer, 2> m_frameBuffers; + uint m_backFb = 0; + void initDumbFrameBuffer(FrameBuffer &fb); + QVector<void (*)()> m_blendFinishedCallbacks; + + struct DmaBuffer { //these are for qt buffers before blending with additional layers (gbm buffer data) + int dmabufFd = -1; + }; + static void dmaBufferDestroyedHandler(gbm_bo *gbmBo, void *data); + DmaBuffer *dmaBufferForGbmBuffer(gbm_bo *gbmBo); + + void ensureModeSet(); + void doDrmFlip(); + + bool m_blendScheduled = false; + int m_qtLayer = 0; //TODO: add API for changing this +}; + +QT_END_NAMESPACE + +#endif // QEGLFSKMSVSP2SCREEN_H diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp new file mode 100644 index 0000000000..25b0c39050 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp @@ -0,0 +1,627 @@ +/**************************************************************************** +** +** 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 "qeglfsintegration_p.h" +#include "qlinuxmediadevice.h" +#include <qeglfskmshelpers.h> + +#include <QtCore/QLoggingCategory> +#include <QtCore/QSize> +#include <QtCore/QRect> + +#include <sys/ioctl.h> +#include <fcntl.h> + +#include <cstdlib> //this needs to go before mediactl/mediactl.h because it uses size_t without including it +extern "C" { +#include <mediactl/mediactl.h> +#include <mediactl/v4l2subdev.h> +} + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) + +static QString mediaBusFmtToString(uint code) +{ + switch (code) { + case MEDIA_BUS_FMT_FIXED: return "FIXED"; + case MEDIA_BUS_FMT_RGB444_1X12: return "RGB444_1X12"; +// case MEDIA_BUS_FMT_RGB444_2X8_PADHI_B: return "RGB444_2X8_PADHI_B"; +// case MEDIA_BUS_FMT_RGB444_2X8_PADHI_L: return "RGB444_2X8_PADHI_L"; +// case MEDIA_BUS_FMT_RGB555_2X8_PADHI_B: return "RGB555_2X8_PADHI_B"; +// case MEDIA_BUS_FMT_RGB555_2X8_PADHI_L: return "RGB555_2X8_PADHI_L"; + case MEDIA_BUS_FMT_RGB565_1X16: return "RGB565_1X16"; + case MEDIA_BUS_FMT_BGR565_2X8_BE: return "BGR565_2X8_BE"; + case MEDIA_BUS_FMT_BGR565_2X8_LE: return "BGR565_2X8_LE"; + case MEDIA_BUS_FMT_RGB565_2X8_BE: return "RGB565_2X8_BE"; + case MEDIA_BUS_FMT_RGB565_2X8_LE: return "RGB565_2X8_LE"; + case MEDIA_BUS_FMT_RGB666_1X18: return "RGB666_1X18"; + case MEDIA_BUS_FMT_RBG888_1X24: return "RBG888_1X24"; +// case MEDIA_BUS_FMT_RGB666_1X24_CPADH: return "RGB666_1X24_CPADH"; + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: return "RGB666_1X7X3_SPWG"; + case MEDIA_BUS_FMT_BGR888_1X24: return "BGR888_1X24"; + case MEDIA_BUS_FMT_GBR888_1X24: return "GBR888_1X24"; + case MEDIA_BUS_FMT_RGB888_1X24: return "RGB888_1X24"; + case MEDIA_BUS_FMT_RGB888_2X12_BE: return "RGB888_2X12_BE"; + case MEDIA_BUS_FMT_RGB888_2X12_LE: return "RGB888_2X12_LE"; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: return "RGB888_1X7X4_SPWG"; +// case MEDIA_BUS_FMT_RGB888_1X7X4_JEID: return "RGB888_1X7X4_JEID"; + case MEDIA_BUS_FMT_ARGB8888_1X32: return "ARGB8888_1X32"; + case MEDIA_BUS_FMT_RGB888_1X32_PADHI: return "RGB888_1X32_PADHI"; +// case MEDIA_BUS_FMT_RGB101010_1X30: return "RGB101010_1X30"; +// case MEDIA_BUS_FMT_RGB121212_1X36: return "RGB121212_1X36"; +// case MEDIA_BUS_FMT_RGB161616_1X48: return "RGB161616_1X48"; + case MEDIA_BUS_FMT_Y8_1X8: return "Y8_1X8"; + case MEDIA_BUS_FMT_UV8_1X8: return "UV8_1X8"; + case MEDIA_BUS_FMT_UYVY8_1_5X8: return "UYVY8_1_5X8"; + case MEDIA_BUS_FMT_VYUY8_1_5X8: return "VYUY8_1_5X8"; + case MEDIA_BUS_FMT_YUYV8_1_5X8: return "YUYV8_1_5X8"; + case MEDIA_BUS_FMT_YVYU8_1_5X8: return "YVYU8_1_5X8"; + case MEDIA_BUS_FMT_UYVY8_2X8: return "UYVY8_2X8"; + case MEDIA_BUS_FMT_VYUY8_2X8: return "VYUY8_2X8"; + case MEDIA_BUS_FMT_YUYV8_2X8: return "YUYV8_2X8"; + case MEDIA_BUS_FMT_YVYU8_2X8: return "YVYU8_2X8"; + case MEDIA_BUS_FMT_Y10_1X10: return "Y10_1X10"; + case MEDIA_BUS_FMT_UYVY10_2X10: return "UYVY10_2X10"; + case MEDIA_BUS_FMT_VYUY10_2X10: return "VYUY10_2X10"; + case MEDIA_BUS_FMT_YUYV10_2X10: return "YUYV10_2X10"; + case MEDIA_BUS_FMT_YVYU10_2X10: return "YVYU10_2X10"; + case MEDIA_BUS_FMT_Y12_1X12: return "Y12_1X12"; + case MEDIA_BUS_FMT_UYVY12_2X12: return "UYVY12_2X12"; + case MEDIA_BUS_FMT_VYUY12_2X12: return "VYUY12_2X12"; + case MEDIA_BUS_FMT_YUYV12_2X12: return "YUYV12_2X12"; + case MEDIA_BUS_FMT_YVYU12_2X12: return "YVYU12_2X12"; + case MEDIA_BUS_FMT_UYVY8_1X16: return "UYVY8_1X16"; + case MEDIA_BUS_FMT_VYUY8_1X16: return "VYUY8_1X16"; + case MEDIA_BUS_FMT_YUYV8_1X16: return "YUYV8_1X16"; + case MEDIA_BUS_FMT_YVYU8_1X16: return "YVYU8_1X16"; + case MEDIA_BUS_FMT_YDYUYDYV8_1X16: return "YDYUYDYV8_1X16"; + case MEDIA_BUS_FMT_UYVY10_1X20: return "UYVY10_1X20"; + case MEDIA_BUS_FMT_VYUY10_1X20: return "VYUY10_1X20"; + case MEDIA_BUS_FMT_YUYV10_1X20: return "YUYV10_1X20"; + case MEDIA_BUS_FMT_YVYU10_1X20: return "YVYU10_1X20"; + case MEDIA_BUS_FMT_VUY8_1X24: return "VUY8_1X24"; + case MEDIA_BUS_FMT_YUV8_1X24: return "YUV8_1X24"; +// case MEDIA_BUS_FMT_UYYVYY8_0_5X24: return "UYYVYY8_0_5X24"; + case MEDIA_BUS_FMT_UYVY12_1X24: return "UYVY12_1X24"; + case MEDIA_BUS_FMT_VYUY12_1X24: return "VYUY12_1X24"; + case MEDIA_BUS_FMT_YUYV12_1X24: return "YUYV12_1X24"; + case MEDIA_BUS_FMT_YVYU12_1X24: return "YVYU12_1X24"; + case MEDIA_BUS_FMT_YUV10_1X30: return "YUV10_1X30"; +// case MEDIA_BUS_FMT_UYYVYY10_0_5X30: return "UYYVYY10_0_5X30"; + case MEDIA_BUS_FMT_AYUV8_1X32: return "AYUV8_1X32"; +// case MEDIA_BUS_FMT_UYYVYY12_0_5X36: return "UYYVYY12_0_5X36"; +// case MEDIA_BUS_FMT_YUV12_1X36: return "YUV12_1X36"; +// case MEDIA_BUS_FMT_YUV16_1X48: return "YUV16_1X48"; +// case MEDIA_BUS_FMT_UYYVYY16_0_5X48: return "UYYVYY16_0_5X48"; + case MEDIA_BUS_FMT_SBGGR8_1X8: return "SBGGR8_1X8"; + case MEDIA_BUS_FMT_SGBRG8_1X8: return "SGBRG8_1X8"; + case MEDIA_BUS_FMT_SGRBG8_1X8: return "SGRBG8_1X8"; + case MEDIA_BUS_FMT_SRGGB8_1X8: return "SRGGB8_1X8"; + case MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8: return "SBGGR10_ALAW8_1X8"; + case MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8: return "SGBRG10_ALAW8_1X8"; + case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8: return "SGRBG10_ALAW8_1X8"; + case MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8: return "SRGGB10_ALAW8_1X8"; + case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8: return "SBGGR10_DPCM8_1X8"; + case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8: return "SGBRG10_DPCM8_1X8"; + case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: return "SGRBG10_DPCM8_1X8"; + case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8: return "SRGGB10_DPCM8_1X8"; +// case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_B: return "SBGGR10_2X8_PADHI_B"; +// case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_L: return "SBGGR10_2X8_PADHI_L"; +// case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_B: return "SBGGR10_2X8_PADLO_B"; +// case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_L: return "SBGGR10_2X8_PADLO_L"; + case MEDIA_BUS_FMT_SBGGR10_1X10: return "SBGGR10_1X10"; + case MEDIA_BUS_FMT_SGBRG10_1X10: return "SGBRG10_1X10"; + case MEDIA_BUS_FMT_SGRBG10_1X10: return "SGRBG10_1X10"; + case MEDIA_BUS_FMT_SRGGB10_1X10: return "SRGGB10_1X10"; + case MEDIA_BUS_FMT_SBGGR12_1X12: return "SBGGR12_1X12"; + case MEDIA_BUS_FMT_SGBRG12_1X12: return "SGBRG12_1X12"; + case MEDIA_BUS_FMT_SGRBG12_1X12: return "SGRBG12_1X12"; + case MEDIA_BUS_FMT_SRGGB12_1X12: return "SRGGB12_1X12"; + case MEDIA_BUS_FMT_SBGGR14_1X14: return "SBGGR14_1X14"; + case MEDIA_BUS_FMT_SGBRG14_1X14: return "SGBRG14_1X14"; + case MEDIA_BUS_FMT_SGRBG14_1X14: return "SGRBG14_1X14"; + case MEDIA_BUS_FMT_SRGGB14_1X14: return "SRGGB14_1X14"; + case MEDIA_BUS_FMT_SBGGR16_1X16: return "SBGGR16_1X16"; + case MEDIA_BUS_FMT_SGBRG16_1X16: return "SGBRG16_1X16"; + case MEDIA_BUS_FMT_SGRBG16_1X16: return "SGRBG16_1X16"; + case MEDIA_BUS_FMT_SRGGB16_1X16: return "SRGGB16_1X16"; + case MEDIA_BUS_FMT_JPEG_1X8: return "JPEG_1X8"; + case MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8: return "S5C_UYVY_JPEG_1X8"; + case MEDIA_BUS_FMT_AHSV8888_1X32: return "AHSV8888_1X32"; + default: return QString(code); + } +} + +static QDebug operator<<(QDebug debug, const struct v4l2_mbus_framefmt &format) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "v4l2_mbus_framefmt(" + << "code: " << mediaBusFmtToString(format.code) << ", " + << "size: " << format.width << "x" << format.height << ")"; + return debug; +} + +static QDebug operator<<(QDebug debug, const struct v4l2_pix_format_mplane &format) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "v4l2_pix_format_mplane(" + << "pixel format: " << q_fourccToString(format.pixelformat) << ", " + << "size: " << format.width << "x" << format.height << ", " + << "planes: " << format.num_planes << ")"; + return debug; +} + +QLinuxMediaDevice::QLinuxMediaDevice(const QString &devicePath) + : m_mediaDevice(media_device_new(devicePath.toStdString().c_str())) +{ + if (!m_mediaDevice) + qFatal("Couldn't get media device"); + + if (media_device_enumerate(m_mediaDevice)) + qFatal("Couldn't enumerate media device"); + + m_info = media_get_info(m_mediaDevice); + + qCDebug(qLcEglfsKmsDebug) << "Opened linux media device:" + << "\n\t Path:" << devicePath + << "\n\t Model:" << model() + << "\n\t Device name:" << deviceName(); + + resetLinks(); +} + +QLinuxMediaDevice::~QLinuxMediaDevice() +{ + if (m_mediaDevice) + media_device_unref(m_mediaDevice); +} + +QString QLinuxMediaDevice::model() +{ + return QString(m_info->model); +} + +QString QLinuxMediaDevice::deviceName() +{ + return QString(m_info->bus_info).split(":").last(); +} + +bool QLinuxMediaDevice::resetLinks() +{ + if (media_reset_links(m_mediaDevice)) { + qWarning() << "Could not reset media controller links."; + return false; + } + qCDebug(qLcEglfsKmsDebug) << "Reset media links"; + return true; +} + +struct media_link *QLinuxMediaDevice::parseLink(const QString &link) +{ + char *endp = nullptr;; + struct media_link *mediaLink = media_parse_link(m_mediaDevice, link.toStdString().c_str(), &endp); + + if (!mediaLink) + qWarning() << "Failed to parse media link:" << link; + + return mediaLink; +} + +struct media_pad *QLinuxMediaDevice::parsePad(const QString &pad) +{ + struct media_pad *mediaPad = media_parse_pad(m_mediaDevice, pad.toStdString().c_str(), nullptr); + + if (!mediaPad) + qWarning() << "Failed to parse media pad:" << pad; + + return mediaPad; +} + +bool QLinuxMediaDevice::enableLink(struct media_link *link) +{ + if (media_setup_link(m_mediaDevice, link->source, link->sink, 1)) { + qWarning() << "Failed to enable media link."; + return false; + } + return true; +} + +bool QLinuxMediaDevice::disableLink(struct media_link *link) +{ + if (media_setup_link(m_mediaDevice, link->source, link->sink, 0)) { + qWarning() << "Failed to disable media link."; + return false; + } + return true; +} + +media_entity *QLinuxMediaDevice::getEntity(const QString &name) +{ + struct media_entity *entity = media_get_entity_by_name(m_mediaDevice, name.toStdString().c_str(), name.length()); + + if (!entity) + qWarning() << "Failed to get media entity:" << name; + + return entity; +} + +int QLinuxMediaDevice::openVideoDevice(const QString &name) +{ + struct media_entity *entity = getEntity(name); + const char *deviceName = media_entity_get_devname(entity); + int fd = open(deviceName, O_RDWR); + qCDebug(qLcEglfsKmsDebug) << "Opened video device:" << deviceName << "with fd" << fd; + return fd; +} + +QLinuxMediaDevice::CaptureSubDevice::CaptureSubDevice(QLinuxMediaDevice *mediaDevice, const QString &name) + : m_subdevFd(mediaDevice->openVideoDevice(name)) +{ +} + +bool QLinuxMediaDevice::CaptureSubDevice::setFormat(const QSize &size, uint32_t pixelFormat) +{ + Q_ASSERT(size.isValid()); + struct v4l2_format format; + memset(&format, 0, sizeof(struct v4l2_format)); + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + if (ioctl(m_subdevFd, VIDIOC_G_FMT, &format) == -1) { + qErrnoWarning("VIDIOC_G_FMT for capture device failed"); + return false; + } + + format.fmt.pix_mp.width = static_cast<uint>(size.width()); + format.fmt.pix_mp.height = static_cast<uint>(size.height()); + format.fmt.pix_mp.field = V4L2_FIELD_NONE; + format.fmt.pix_mp.pixelformat = pixelFormat; + format.fmt.pix_mp.num_planes = 1; + format.fmt.pix_mp.flags = 0; + format.fmt.pix_mp.plane_fmt[0].bytesperline = 0; + format.fmt.pix_mp.plane_fmt[0].sizeimage = 0; + + if (ioctl(m_subdevFd, VIDIOC_S_FMT, &format) == -1) { + qWarning() << "Capture device" << m_subdevFd << "VIDIOC_S_FMT with format" << format.fmt.pix_mp + << "failed:" << strerror(errno); + return false; + } + + qCDebug(qLcEglfsKmsDebug) << "Capture device" << m_subdevFd << "format set:" << format.fmt.pix_mp; + return true; +} + +bool QLinuxMediaDevice::CaptureSubDevice::clearBuffers() +{ + struct v4l2_requestbuffers requestBuffers; + memset(&requestBuffers, 0, sizeof(requestBuffers)); + requestBuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + requestBuffers.memory = V4L2_MEMORY_DMABUF; + requestBuffers.count = 0; + if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) { + qWarning("Capture device %d: VIDIOC_REQBUFS clear failed: %s", m_subdevFd, strerror(errno)); + return false; + } + qCDebug(qLcEglfsKmsDebug, "Capture device %d: Deallocced buffers with REQBUF, now has %d buffers", m_subdevFd, requestBuffers.count); + Q_ASSERT(requestBuffers.count == 0); + return true; +} + +bool QLinuxMediaDevice::CaptureSubDevice::requestBuffer() +{ + struct v4l2_requestbuffers requestBuffers; + memset(&requestBuffers, 0, sizeof(requestBuffers)); + requestBuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + requestBuffers.memory = V4L2_MEMORY_DMABUF; + requestBuffers.count = 1; + if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) { + if (errno == EINVAL) + qWarning("Capture device %d: Multi-planar capture or dma buffers not supported", m_subdevFd); + qWarning("Capture device %d: VIDIOC_REQBUFS failed: %s", m_subdevFd, strerror(errno)); + return false; + } + Q_ASSERT(requestBuffers.count == 1); + return true; +} + +bool QLinuxMediaDevice::CaptureSubDevice::queueBuffer(int dmabufFd, const QSize &bufferSize) +{ + const uint numPlanes = 1; + struct v4l2_buffer buffer; + memset(&buffer, 0, sizeof(buffer)); + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buffer.memory = V4L2_MEMORY_DMABUF; + buffer.index = 0; + + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + buffer.m.planes = planes; + buffer.length = numPlanes; + memset(planes, 0, sizeof(planes)); + for (uint i = 0; i < numPlanes; i++) { + buffer.m.planes[i].m.fd = dmabufFd; + buffer.m.planes[i].length = static_cast<uint>(bufferSize.width() * bufferSize.height() * 4); //TODO: don't harcode bpp + buffer.m.planes[i].bytesused = static_cast<uint>(bufferSize.width() * bufferSize.height() * 4); //TODO: don't harcode bpp + } + + if (ioctl(m_subdevFd, VIDIOC_QBUF, &buffer) == -1) { + qWarning("Capture device %d: VIDIOC_QBUF failed for dma buffer with fd %d: %s", + m_subdevFd, dmabufFd, strerror(errno)); + return false; + } + + return true; +} + +bool QLinuxMediaDevice::CaptureSubDevice::dequeueBuffer() +{ + const int numPlanes = 1; + struct v4l2_buffer buffer; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + + memset(&buffer, 0, sizeof(buffer)); + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buffer.memory = V4L2_MEMORY_DMABUF; + buffer.index = 0; + buffer.m.planes = planes; + buffer.length = numPlanes; + memset(planes, 0, sizeof(planes)); + + if (ioctl(m_subdevFd, VIDIOC_DQBUF, &buffer) == -1) { + qWarning("Capture device %d: VIDIOC_DQBUF failed: %s", m_subdevFd, strerror(errno)); + return false; + } + + return true; +} + +bool QLinuxMediaDevice::CaptureSubDevice::streamOn() +{ + return QLinuxMediaDevice::streamOn(m_subdevFd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); +} + +bool QLinuxMediaDevice::CaptureSubDevice::streamOff() +{ + return QLinuxMediaDevice::streamOff(m_subdevFd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); +} + +QLinuxMediaDevice::OutputSubDevice::OutputSubDevice(QLinuxMediaDevice *mediaDevice, const QString &name) + : m_subdevFd(mediaDevice->openVideoDevice(name)) +{ +} + +bool QLinuxMediaDevice::OutputSubDevice::setFormat(const QSize &size, uint32_t pixelFormat, uint32_t bytesPerLine) +{ + Q_ASSERT(size.isValid()); + struct v4l2_format format; + memset(&format, 0, sizeof(struct v4l2_format)); + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + if (ioctl(m_subdevFd, VIDIOC_G_FMT, &format) == -1) { + qErrnoWarning("VIDIOC_G_FMT for output device failed"); + return false; + } + + format.fmt.pix_mp.width = static_cast<uint>(size.width()); + format.fmt.pix_mp.height = static_cast<uint>(size.height()); + format.fmt.pix_mp.field = V4L2_FIELD_NONE; + format.fmt.pix_mp.pixelformat = pixelFormat; + format.fmt.pix_mp.num_planes = 1; + format.fmt.pix_mp.flags = 0; + format.fmt.pix_mp.plane_fmt[0].bytesperline = bytesPerLine; + format.fmt.pix_mp.plane_fmt[0].sizeimage = 0; + + if (ioctl(m_subdevFd, VIDIOC_S_FMT, &format) == -1) { + qWarning() << "Output device" << m_subdevFd << "VIDIOC_S_FMT with format" << format.fmt.pix_mp + << "failed:" << strerror(errno); + return false; + } + + qCDebug(qLcEglfsKmsDebug) << "Output device device" << m_subdevFd << "format set:" << format.fmt.pix_mp; + return true; +} + +bool QLinuxMediaDevice::OutputSubDevice::clearBuffers() +{ + struct v4l2_requestbuffers requestBuffers; + memset(&requestBuffers, 0, sizeof(requestBuffers)); + requestBuffers.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + requestBuffers.memory = V4L2_MEMORY_DMABUF; + requestBuffers.count = 0; + if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) { + qWarning("Output device %d: VIDIOC_REQBUFS clear failed: %s", m_subdevFd, strerror(errno)); + return false; + } + qCDebug(qLcEglfsKmsDebug, "Output device %d: Deallocced buffers with REQBUF, now has %d buffers", m_subdevFd, requestBuffers.count); + Q_ASSERT(requestBuffers.count == 0); + return true; +} + +bool QLinuxMediaDevice::OutputSubDevice::requestBuffer() +{ + struct v4l2_requestbuffers requestBuffers; + memset(&requestBuffers, 0, sizeof(requestBuffers)); + requestBuffers.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + requestBuffers.memory = V4L2_MEMORY_DMABUF; + requestBuffers.count = 1; + if (ioctl(m_subdevFd, VIDIOC_REQBUFS, &requestBuffers) == -1) { + if (errno == EINVAL) + qWarning("Output device %d: Multi-planar output or dma buffers not supported", m_subdevFd); + qWarning("Output device %d: VIDIOC_REQBUFS failed: %s", m_subdevFd, strerror(errno)); + return false; + } + qCDebug(qLcEglfsKmsDebug) << "REQBUF returned" << requestBuffers.count << "buffers for output device" << m_subdevFd; + return true; +} + +bool QLinuxMediaDevice::OutputSubDevice::queueBuffer(int dmabufFd, uint bytesUsed, uint length) +{ + const int numPlanes = 1; + struct v4l2_buffer buffer; + memset(&buffer, 0, sizeof(buffer)); + buffer.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + buffer.memory = V4L2_MEMORY_DMABUF; + buffer.index = 0; + buffer.length = numPlanes; + buffer.bytesused = bytesUsed; + buffer.field = V4L2_FIELD_NONE; //TODO: what is this? + + struct v4l2_plane planes[numPlanes]; + memset(planes, 0, sizeof(planes)); + buffer.m.planes = planes; + + for (int i = 0; i < numPlanes; i++) { + buffer.m.planes[i].m.fd = dmabufFd; + buffer.m.planes[i].length = length; + buffer.m.planes[i].bytesused = bytesUsed; + } + + if (ioctl(m_subdevFd, VIDIOC_QBUF, &buffer) == -1) { + qWarning("Output device %d: VIDIOC_QBUF failed for dmabuf %d: %s", m_subdevFd, dmabufFd, strerror(errno)); + return false; + } + + if (!(buffer.flags & V4L2_BUF_FLAG_QUEUED)) { + qWarning() << "Queued flag not set on buffer for output device"; + return false; + } + + return true; +} + +bool QLinuxMediaDevice::OutputSubDevice::streamOn() +{ + return QLinuxMediaDevice::streamOn(m_subdevFd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); +} + +bool QLinuxMediaDevice::OutputSubDevice::streamOff() +{ + return QLinuxMediaDevice::streamOff(m_subdevFd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); +} + +int QLinuxMediaDevice::openVideoDevice(media_pad *pad) +{ + const char *deviceName = media_entity_get_devname(pad->entity); + int fd = open(deviceName, O_RDWR); + qCDebug(qLcEglfsKmsDebug) << "Opened video device:" << deviceName << "with fd" << fd; + return fd; +} + +bool QLinuxMediaDevice::setSubdevFormat(struct media_pad *pad, const QSize &size, uint32_t mbusFormat) +{ + Q_ASSERT(size.isValid()); + struct v4l2_mbus_framefmt format; + format.width = static_cast<uint>(size.width()); + format.height = static_cast<uint>(size.height()); + format.code = mbusFormat; + + if (v4l2_subdev_set_format(pad->entity, &format, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE)) { + qWarning() << "Setting v4l2_subdev_set_format failed for format" << format; + return false; + } + + if (format.code != mbusFormat) { + qWarning() << "Got" << mediaBusFmtToString(format.code) << "instead of" + << mediaBusFmtToString(mbusFormat) << "when setting subdevice format"; + return false; + } + + qCDebug(qLcEglfsKmsDebug) << "Set format to" << format << "for entity" << pad->entity << "index" << pad->index; + return true; +} + +bool QLinuxMediaDevice::setSubdevAlpha(int subdevFd, qreal alpha) +{ + struct v4l2_control control; + control.id = V4L2_CID_ALPHA_COMPONENT; + control.value = static_cast<__s32>(alpha * 0xff); + if (ioctl(subdevFd, VIDIOC_S_CTRL, &control) == -1) { + qErrnoWarning("Setting alpha (%d) failed", control.value); + return false; + } + return true; +} + +bool QLinuxMediaDevice::setSubdevSelection(struct media_pad *pad, const QRect &geometry, uint target) +{ + Q_ASSERT(geometry.isValid()); + struct v4l2_rect rect; + rect.left = geometry.left(); + rect.top = geometry.top(); + rect.width = static_cast<uint>(geometry.width()); + rect.height = static_cast<uint>(geometry.height()); + + int ret = v4l2_subdev_set_selection(pad->entity, &rect, pad->index, target, V4L2_SUBDEV_FORMAT_ACTIVE); + if (ret) { + qWarning() << "Setting subdev selection failed."; + return false; + } + + return true; +} + +bool QLinuxMediaDevice::setSubdevCrop(struct media_pad *pad, const QRect &geometry) +{ + return setSubdevSelection(pad, geometry, V4L2_SEL_TGT_CROP); +} + +bool QLinuxMediaDevice::setSubdevCompose(struct media_pad *pad, const QRect &geometry) +{ + return setSubdevSelection(pad, geometry, V4L2_SEL_TGT_COMPOSE); +} + +bool QLinuxMediaDevice::streamOn(int subDeviceFd, v4l2_buf_type bufferType) +{ + if (ioctl(subDeviceFd, VIDIOC_STREAMON, &bufferType) == -1) { + qWarning("VIDIOC_STREAMON failed for subdevice %d: %s", subDeviceFd, strerror(errno)); + return false; + } + + return true; +} + +bool QLinuxMediaDevice::streamOff(int subDeviceFd, v4l2_buf_type bufferType) +{ + if (ioctl(subDeviceFd, VIDIOC_STREAMOFF, &bufferType) == -1) { + qWarning("VIDIOC_STREAMOFF failed for subdevice %d: %s", subDeviceFd, strerror(errno)); + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.h new file mode 100644 index 0000000000..26f863214b --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 QLINUXMEDIADEVICE_H +#define QLINUXMEDIADEVICE_H + +#include <linux/v4l2-mediabus.h> + +#include <QtCore/qglobal.h> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +class QSize; +class QRect; + +class QLinuxMediaDevice +{ +public: + QLinuxMediaDevice(const QString &devicePath); + ~QLinuxMediaDevice(); + + QString model(); + QString deviceName(); + bool resetLinks(); + struct media_link *parseLink(const QString &link); + struct media_pad *parsePad(const QString &pad); + bool enableLink(struct media_link *link); + bool disableLink(struct media_link *link); + struct media_entity *getEntity(const QString &name); + int openVideoDevice(const QString &name); + + class CaptureSubDevice + { + public: + CaptureSubDevice(QLinuxMediaDevice *mediaDevice, const QString &name); + bool setFormat(const QSize &size, uint32_t pixelFormat = V4L2_PIX_FMT_ABGR32); //TODO: fix to match output device + bool clearBuffers(); + bool requestBuffer(); + bool queueBuffer(int dmabufFd, const QSize &bufferSize); + bool dequeueBuffer(); + bool streamOn(); + bool streamOff(); + private: + int m_subdevFd = -1; + }; + + class OutputSubDevice + { + public: + OutputSubDevice(QLinuxMediaDevice *mediaDevice, const QString &name); + bool setFormat(const QSize &size, uint32_t pixelFormat, uint32_t bytesPerLine); + bool clearBuffers(); + bool requestBuffer(); + bool queueBuffer(int dmabufFd, uint bytesUsed, uint length); + bool streamOn(); + bool streamOff(); + private: + int m_subdevFd = -1; + }; + + static int openVideoDevice(media_pad *pad); + + static bool setSubdevFormat(struct media_pad *pad, const QSize &size, uint32_t mbusFormat = MEDIA_BUS_FMT_ARGB8888_1X32); + static bool setSubdevAlpha(int subdevFd, qreal alpha); + + static bool setSubdevSelection(struct media_pad *pad, const QRect &geometry, uint target); + static bool setSubdevCrop(struct media_pad *pad, const QRect &geometry); + static bool setSubdevCompose(struct media_pad *pad, const QRect &geometry); + +private: + static bool streamOn(int subDeviceFd, v4l2_buf_type bufferType); + static bool streamOff(int subDeviceFd, v4l2_buf_type bufferType); + struct media_device *m_mediaDevice = nullptr; + const struct media_device_info *m_info = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QLINUXMEDIADEVICE_H diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp new file mode 100644 index 0000000000..879d312341 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** 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 "qvsp2blendingdevice.h" +#include <qeglfskmshelpers.h> + +#include <QDebug> +#include <QtCore/QLoggingCategory> + +#include <drm_fourcc.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) + +//TODO: is this the right place for these conversion functions? +static inline uint drmFormatToV4l2PixelFormat(uint drmFormat) { + //ARGB8888 == ABGR32 because linux media list stuff in the opposite order, but the fourcc is the same + Q_ASSERT(DRM_FORMAT_ARGB8888 == V4L2_PIX_FMT_ABGR32); + return drmFormat; +} + +static uint drmFormatToMediaBusFormat(uint drmFormat) +{ + switch (drmFormat) { + case DRM_FORMAT_RGB888: + return MEDIA_BUS_FMT_RGB888_1X24; + case DRM_FORMAT_BGR888: + return MEDIA_BUS_FMT_BGR888_1X24; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: +// return MEDIA_BUS_FMT_RGB888_1X32_PADHI; // doesn't work on renesas m3, just use fallthrough to argb for now + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + return MEDIA_BUS_FMT_ARGB8888_1X32; + default: + qWarning() << "Unknown drm format" << q_fourccToString(drmFormat) << "defaulting to argb8888"; + return MEDIA_BUS_FMT_ARGB8888_1X32; + } +} + +QVsp2BlendingDevice::QVsp2BlendingDevice(const QSize &screenSize) + : m_mediaDevice("/dev/media0") + , m_screenSize(screenSize) +{ + QLinuxMediaDevice &md = m_mediaDevice; + QString deviceName = md.deviceName(); + + if (md.model() != QString("VSP2")) + qWarning() << "Unsupported media device model:" << md.model(); + + if (deviceName != "fe960000.vsp") + qWarning() << "Unknown media device name:" << deviceName; + + const int numInputs = 5; + + for (int i = 0; i < numInputs; ++i) { + Input input; + input.linkToBru = md.parseLink(QString("'%1 rpf.%2':1 -> '%1 bru':%2").arg(deviceName).arg(i)); + input.inputFormatPad = md.parsePad(QString("'%1 rpf.%2':0").arg(deviceName).arg(i)); + input.outputFormatPad = md.parsePad(QString("'%1 rpf.%2':1").arg(deviceName).arg(i)); + input.outputFormatFd = QLinuxMediaDevice::openVideoDevice(input.outputFormatPad); + input.bruInputFormatPad = md.parsePad(QString("'%1 bru':%2").arg(deviceName).arg(i)); + input.rpfInput = new QLinuxMediaDevice::OutputSubDevice(&md, QString("%1 rpf.%2 input").arg(deviceName).arg(i)); + m_inputs.append(input); + } + + m_wpfOutput = new QLinuxMediaDevice::CaptureSubDevice(&md, QString("%1 wpf.0 output").arg(deviceName)); + + // Setup links for output + md.enableLink(md.parseLink(QString("'%1 bru':5 -> '%1 wpf.0':0").arg(deviceName))); + md.enableLink(md.parseLink(QString("'%1 wpf.0':1 -> '%1 wpf.0 output':0").arg(deviceName))); + + // Output pads + auto bruOutputFormatPad = md.parsePad(QString("'%1 bru':5").arg(deviceName)); + auto wpfInputFormatPad = md.parsePad(QString("'%1 wpf.0':0").arg(deviceName)); + auto wpfOutputFormatPad = md.parsePad(QString("'%1 wpf.0':1").arg(deviceName)); + + m_wpfOutput->setFormat(screenSize); + QLinuxMediaDevice::setSubdevFormat(bruOutputFormatPad, screenSize); + QLinuxMediaDevice::setSubdevFormat(wpfInputFormatPad, screenSize); + QLinuxMediaDevice::setSubdevFormat(wpfOutputFormatPad, screenSize); + + m_wpfOutput->requestBuffer(); +} + +bool QVsp2BlendingDevice::enableInput(int i, const QRect &bufferGeometry, uint drmFormat, uint bytesPerLine) +{ + qCDebug(qLcEglfsKmsDebug) << "Blend unit: Enabling input" << i; + if (m_inputs[i].enabled) { + qWarning("Vsp2: Input %d already enabled", i); + return false; + } + + if (!bufferGeometry.isValid()) { //TODO: bounds checking as well? + qWarning() << "Vsp2: Invalid buffer geometry"; + return false; + } + + Input &input = m_inputs[i]; + if (!m_mediaDevice.enableLink(input.linkToBru)) + return false; + + uint pixelFormat = drmFormatToV4l2PixelFormat(drmFormat); + if (!setInputFormat(i, bufferGeometry, pixelFormat, bytesPerLine)) { + disableInput(i); + return false; + } + + input.rpfInput->requestBuffer(); + return true; +} + +int QVsp2BlendingDevice::enableInput(const QRect &bufferGeometry, uint drmFormat, uint bytesPerLine) +{ + for (int i = 0; i < m_inputs.size(); ++i) { + if (!m_inputs[i].enabled) + return enableInput(i, bufferGeometry, drmFormat, bytesPerLine) ? i : -1; + } + qWarning() << "Vsp2: No more inputs available in blend unit"; + return -1; +} + +bool QVsp2BlendingDevice::disableInput(int i) +{ + qCDebug(qLcEglfsKmsDebug) << "Vsp2: disabling input" << i; + if (!m_inputs[i].enabled) { + qWarning("Vsp2: Input %d already disabled", i); + return false; + } + m_mediaDevice.disableLink(m_inputs[i].linkToBru); + m_inputs[i].rpfInput->clearBuffers(); + m_inputs[i].enabled = false; + return true; +} + +bool QVsp2BlendingDevice::setInputBuffer(int index, int dmabufFd) +{ + Input &input = m_inputs[index]; + + if (!input.enabled) { + qWarning() << "Vsp2: Can't queue on disabled input" << index; + return false; + } + + // Don't queue the buffer yet, store it here and wait until blending + if (input.dmabuf.fd != dmabufFd) { + m_dirty = true; + input.dmabuf.fd = dmabufFd; + } + return true; +} + +bool QVsp2BlendingDevice::setInputPosition(int index, const QPoint &position) +{ + Input &input = m_inputs[index]; + + if (input.geometry.topLeft() == position) + return true; + + m_dirty = true; + input.geometry.moveTopLeft(position); + return QLinuxMediaDevice::setSubdevCompose(input.bruInputFormatPad, input.geometry); +} + +bool QVsp2BlendingDevice::setInputAlpha(int index, qreal alpha) +{ + Input &input = m_inputs[index]; + if (input.alpha == alpha) + return true; + + m_dirty = true; + input.alpha = alpha; + return QLinuxMediaDevice::setSubdevAlpha(input.outputFormatFd, alpha); +} + +bool QVsp2BlendingDevice::blend(int outputDmabufFd) +{ + if (!m_dirty) + qWarning("Blending without being dirty, should not be necessary"); + + if (!m_inputs[0].enabled) { + qWarning("Vsp2: Can't blend with layer 0 disabled"); + return false; + } + + // Queue dma input buffers + for (int i=0; i < m_inputs.size(); ++i) { + auto &input = m_inputs[i]; + if (input.enabled) { + if (!input.rpfInput->queueBuffer(input.dmabuf.fd, input.dmabuf.bytesUsed, input.dmabuf.length)) { + qWarning() << "Vsp2: Failed to queue buffer for input" << i + << "with dmabuf" << input.dmabuf.fd + << "and size" << input.geometry.size(); + + if (!disableInput(i)) + qWarning() << "Vsp2: Failed to disable input" << i; + } + } + } + + if (!m_wpfOutput->queueBuffer(outputDmabufFd, m_screenSize)) { + qWarning() << "Vsp2: Failed to queue blending output buffer" << outputDmabufFd << m_screenSize; + return false; + } + + if (!streamOn()) { + qWarning() << "Vsp2: Failed to start streaming"; + return false; + } + + if (!m_wpfOutput->dequeueBuffer()) { + qWarning() << "Vsp2: Failed to dequeue blending output buffer"; + if (!streamOff()) + qWarning() << "Vsp2: Failed to stop streaming when recovering after a broken blend."; + return false; + } + + if (!streamOff()) { + qWarning() << "Vsp2: Failed to stop streaming"; + return false; + } + + m_dirty = false; + return true; +} + +int QVsp2BlendingDevice::numInputs() const +{ + return m_inputs.size(); +} + +bool QVsp2BlendingDevice::streamOn() +{ + for (auto &input : m_inputs) { + if (input.enabled) { + if (!input.rpfInput->streamOn()) { + //TODO: perhaps it's better to try to continue with the other inputs? + return false; + } + } + } + + return m_wpfOutput->streamOn(); +} + +bool QVsp2BlendingDevice::streamOff() +{ + bool succeeded = m_wpfOutput->streamOff(); + for (auto &input : m_inputs) { + if (input.enabled) + succeeded &= input.rpfInput->streamOff(); + } + return succeeded; +} + +bool QVsp2BlendingDevice::setInputFormat(int i, const QRect &bufferGeometry, uint pixelFormat, uint bytesPerLine) +{ + Input &input = m_inputs[i]; + + Q_ASSERT(bufferGeometry.isValid()); + + const uint bpp = 4; //TODO: don't hardcode bpp, get it from pixelFormat? + input.enabled = true; + input.geometry = bufferGeometry; + input.dmabuf.bytesUsed = bpp * static_cast<uint>(bufferGeometry.width()) * static_cast<uint>(bufferGeometry.height()); + input.dmabuf.length = static_cast<uint>(bufferGeometry.height()) * bytesPerLine; + + const QSize size = bufferGeometry.size(); + + if (!input.rpfInput->setFormat(size, pixelFormat, bytesPerLine)) // rpf.x input + return false; + + const uint mediaBusFormat = drmFormatToMediaBusFormat(pixelFormat); + if (!QLinuxMediaDevice::setSubdevFormat(input.inputFormatPad, size, mediaBusFormat)) // rpf.x:0 + return false; + + if (!QLinuxMediaDevice::setSubdevFormat(input.outputFormatPad, size, mediaBusFormat)) // rpf.x:1 + return false; + + if (!QLinuxMediaDevice::setSubdevFormat(input.bruInputFormatPad, size, mediaBusFormat)) // bru:x + return false; + + if (!QLinuxMediaDevice::setSubdevCrop(input.inputFormatPad, QRect(QPoint(0, 0), size))) + return false; + + if (!QLinuxMediaDevice::setSubdevCompose(input.bruInputFormatPad, bufferGeometry)) + return false; + + return true; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.h new file mode 100644 index 0000000000..be48954f47 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 QVSP2BLENDINGDEVICE_H +#define QVSP2BLENDINGDEVICE_H + +#include <QtCore/QRect> +#include <QtCore/QVector> +#include <QtCore/qglobal.h> + +#include "qlinuxmediadevice.h" + +QT_BEGIN_NAMESPACE + +class QSize; + +class QVsp2BlendingDevice +{ +public: + QVsp2BlendingDevice(const QSize& screenSize); //TODO: add support for output format as well? + bool enableInput(int i, const QRect &bufferGeometry, uint drmFormat, uint bytesPerLine); + int enableInput(const QRect &bufferGeometry, uint drmFormat, uint bytesPerLine); + bool disableInput(int i); + bool setInputBuffer(int index, int dmabufFd); + bool setInputPosition(int index, const QPoint &position); + bool setInputAlpha(int index, qreal alpha); + bool blend(int outputDmabufFd); + int numInputs() const; + bool isDirty() const { return m_dirty; } +private: + bool streamOn(); + bool streamOff(); + bool setInputFormat(int i, const QRect &bufferGeometry, uint pixelFormat, uint bytesPerLine); + QLinuxMediaDevice m_mediaDevice; + QLinuxMediaDevice::CaptureSubDevice *m_wpfOutput = nullptr; // wpf output + struct Input { + bool enabled = false; + QRect geometry; + qreal alpha = 1; + struct { + int fd = -1; + uint bytesUsed = 0; + uint length = 0; + } dmabuf; + struct media_link *linkToBru = nullptr; //rpf.x:1 -> bru:x + struct media_pad *inputFormatPad = nullptr; // rpf.x:0 + struct media_pad *outputFormatPad = nullptr; // rpf.x:1 + int outputFormatFd = -1; // rpf.x:1 (again, because v4l2_subdev_* doesn't have a way to set alpha) + struct media_pad *bruInputFormatPad = nullptr; // bru:x + QLinuxMediaDevice::OutputSubDevice *rpfInput = nullptr; // rpf.x input + }; + QVector<struct Input> m_inputs; + const QSize m_screenSize; + bool m_dirty = true; +}; + +QT_END_NAMESPACE + +#endif // QVSP2BLENDINGDEVICE 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/main.cpp b/src/plugins/platforms/haiku/main.cpp index 02168d0165..841891970d 100644 --- a/src/plugins/platforms/haiku/main.cpp +++ b/src/plugins/platforms/haiku/main.cpp @@ -47,7 +47,7 @@ QPlatformIntegration *QHaikuIntegrationPlugin::create(const QString& system, con if (!system.compare(QLatin1String("haiku"), Qt::CaseInsensitive)) return new QHaikuIntegration(paramList); - return Q_NULLPTR; + return nullptr; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/haiku/main.h b/src/plugins/platforms/haiku/main.h index 82f3313652..e316b79d7c 100644 --- a/src/plugins/platforms/haiku/main.h +++ b/src/plugins/platforms/haiku/main.h @@ -47,7 +47,7 @@ class QHaikuIntegrationPlugin : public QPlatformIntegrationPlugin Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "haiku.json") public: - QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; + QPlatformIntegration *create(const QString&, const QStringList&) override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/haiku/qhaikuapplication.h b/src/plugins/platforms/haiku/qhaikuapplication.h index 0696df8109..a9ea442691 100644 --- a/src/plugins/platforms/haiku/qhaikuapplication.h +++ b/src/plugins/platforms/haiku/qhaikuapplication.h @@ -49,8 +49,8 @@ class QHaikuApplication : public BApplication public: explicit QHaikuApplication(const char *signature); - bool QuitRequested() Q_DECL_OVERRIDE; - void RefsReceived(BMessage* message) Q_DECL_OVERRIDE; + bool QuitRequested() override; + void RefsReceived(BMessage* message) override; }; #endif diff --git a/src/plugins/platforms/haiku/qhaikubuffer.cpp b/src/plugins/platforms/haiku/qhaikubuffer.cpp index c6f6ffe6bc..f25ddef86b 100644 --- a/src/plugins/platforms/haiku/qhaikubuffer.cpp +++ b/src/plugins/platforms/haiku/qhaikubuffer.cpp @@ -45,7 +45,7 @@ QT_BEGIN_NAMESPACE QHaikuBuffer::QHaikuBuffer() - : m_buffer(Q_NULLPTR) + : m_buffer(nullptr) { } @@ -63,12 +63,12 @@ BBitmap* QHaikuBuffer::nativeBuffer() const const QImage *QHaikuBuffer::image() const { - return (m_buffer != Q_NULLPTR) ? &m_image : Q_NULLPTR; + return (m_buffer != nullptr) ? &m_image : nullptr; } QImage *QHaikuBuffer::image() { - return (m_buffer != Q_NULLPTR) ? &m_image : Q_NULLPTR; + return (m_buffer != nullptr) ? &m_image : nullptr; } QRect QHaikuBuffer::rect() const diff --git a/src/plugins/platforms/haiku/qhaikuclipboard.cpp b/src/plugins/platforms/haiku/qhaikuclipboard.cpp index 774da4432a..20519e21d0 100644 --- a/src/plugins/platforms/haiku/qhaikuclipboard.cpp +++ b/src/plugins/platforms/haiku/qhaikuclipboard.cpp @@ -47,8 +47,8 @@ #include <Clipboard.h> QHaikuClipboard::QHaikuClipboard() - : m_systemMimeData(Q_NULLPTR) - , m_userMimeData(Q_NULLPTR) + : m_systemMimeData(nullptr) + , m_userMimeData(nullptr) { if (be_clipboard) be_clipboard->StartWatching(BMessenger(this)); @@ -81,12 +81,12 @@ QMimeData *QHaikuClipboard::mimeData(QClipboard::Mode mode) const BMessage *clipboard = be_clipboard->Data(); if (clipboard) { - char *name = Q_NULLPTR; + char *name = nullptr; uint32 type = 0; int32 count = 0; for (int i = 0; clipboard->GetInfo(B_MIME_TYPE, i, &name, &type, &count) == B_OK; i++) { - const void *data = Q_NULLPTR; + const void *data = nullptr; int32 dataLen = 0; const status_t status = clipboard->FindData(name, B_MIME_TYPE, &data, &dataLen); @@ -162,7 +162,7 @@ void QHaikuClipboard::MessageReceived(BMessage* message) { if (message->what == B_CLIPBOARD_CHANGED) { delete m_userMimeData; - m_userMimeData = Q_NULLPTR; + m_userMimeData = nullptr; emitChanged(QClipboard::Clipboard); } diff --git a/src/plugins/platforms/haiku/qhaikuclipboard.h b/src/plugins/platforms/haiku/qhaikuclipboard.h index 3dd4496e8d..b6eb3f591f 100644 --- a/src/plugins/platforms/haiku/qhaikuclipboard.h +++ b/src/plugins/platforms/haiku/qhaikuclipboard.h @@ -54,13 +54,13 @@ public: QHaikuClipboard(); ~QHaikuClipboard(); - QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE; - void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE; - bool supportsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE; - bool ownsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE; + QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; + void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; + bool supportsMode(QClipboard::Mode mode) const override; + bool ownsMode(QClipboard::Mode mode) const override; // override from BHandler to catch change notifications from Haiku clipboard - void MessageReceived(BMessage* message) Q_DECL_OVERRIDE; + void MessageReceived(BMessage* message) override; private: QMimeData *m_systemMimeData; diff --git a/src/plugins/platforms/haiku/qhaikucursor.h b/src/plugins/platforms/haiku/qhaikucursor.h index 5d70c97d9e..73a1d2c492 100644 --- a/src/plugins/platforms/haiku/qhaikucursor.h +++ b/src/plugins/platforms/haiku/qhaikucursor.h @@ -52,7 +52,7 @@ public: QHaikuCursor(); #ifndef QT_NO_CURSOR - void changeCursor(QCursor *windowCursor, QWindow *window) Q_DECL_OVERRIDE; + void changeCursor(QCursor *windowCursor, QWindow *window) override; #endif private: diff --git a/src/plugins/platforms/haiku/qhaikuintegration.cpp b/src/plugins/platforms/haiku/qhaikuintegration.cpp index d46d77ff18..8bd2171794 100644 --- a/src/plugins/platforms/haiku/qhaikuintegration.cpp +++ b/src/plugins/platforms/haiku/qhaikuintegration.cpp @@ -87,13 +87,13 @@ QHaikuIntegration::QHaikuIntegration(const QStringList ¶meters) QHaikuIntegration::~QHaikuIntegration() { destroyScreen(m_screen); - m_screen = Q_NULLPTR; + m_screen = nullptr; delete m_services; - m_services = Q_NULLPTR; + m_services = nullptr; delete m_clipboard; - m_clipboard = Q_NULLPTR; + m_clipboard = nullptr; be_app->LockLooper(); be_app->Quit(); diff --git a/src/plugins/platforms/haiku/qhaikuintegration.h b/src/plugins/platforms/haiku/qhaikuintegration.h index 1b938acb82..5c7a173c91 100644 --- a/src/plugins/platforms/haiku/qhaikuintegration.h +++ b/src/plugins/platforms/haiku/qhaikuintegration.h @@ -54,17 +54,17 @@ public: explicit QHaikuIntegration(const QStringList ¶mList); ~QHaikuIntegration(); - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; + bool hasCapability(QPlatformIntegration::Capability cap) const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; + QAbstractEventDispatcher *createEventDispatcher() const override; - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; - QPlatformServices *services() const Q_DECL_OVERRIDE; + QPlatformFontDatabase *fontDatabase() const override; + QPlatformServices *services() const override; #ifndef QT_NO_CLIPBOARD - QPlatformClipboard *clipboard() const Q_DECL_OVERRIDE; + QPlatformClipboard *clipboard() const override; #endif private: diff --git a/src/plugins/platforms/haiku/qhaikurasterbackingstore.cpp b/src/plugins/platforms/haiku/qhaikurasterbackingstore.cpp index ee53f693cd..613ae471cb 100644 --- a/src/plugins/platforms/haiku/qhaikurasterbackingstore.cpp +++ b/src/plugins/platforms/haiku/qhaikurasterbackingstore.cpp @@ -47,14 +47,14 @@ QT_BEGIN_NAMESPACE QHaikuRasterBackingStore::QHaikuRasterBackingStore(QWindow *window) : QPlatformBackingStore(window) - , m_bitmap(Q_NULLPTR) + , m_bitmap(nullptr) { } QHaikuRasterBackingStore::~QHaikuRasterBackingStore() { delete m_bitmap; - m_bitmap = Q_NULLPTR; + m_bitmap = nullptr; } QPaintDevice *QHaikuRasterBackingStore::paintDevice() @@ -62,7 +62,7 @@ QPaintDevice *QHaikuRasterBackingStore::paintDevice() if (!m_bufferSize.isEmpty() && m_bitmap) return m_buffer.image(); - return Q_NULLPTR; + return nullptr; } void QHaikuRasterBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) diff --git a/src/plugins/platforms/haiku/qhaikurasterbackingstore.h b/src/plugins/platforms/haiku/qhaikurasterbackingstore.h index 06a46e7eb3..060ab27126 100644 --- a/src/plugins/platforms/haiku/qhaikurasterbackingstore.h +++ b/src/plugins/platforms/haiku/qhaikurasterbackingstore.h @@ -55,9 +55,9 @@ public: explicit QHaikuRasterBackingStore(QWindow *window); ~QHaikuRasterBackingStore(); - QPaintDevice *paintDevice() Q_DECL_OVERRIDE; - void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - void resize(const QSize &size, const QRegion &staticContents) Q_DECL_OVERRIDE; + QPaintDevice *paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; private: BBitmap *m_bitmap; diff --git a/src/plugins/platforms/haiku/qhaikurasterwindow.cpp b/src/plugins/platforms/haiku/qhaikurasterwindow.cpp index 9834b7cbc7..7e57e67bab 100644 --- a/src/plugins/platforms/haiku/qhaikurasterwindow.cpp +++ b/src/plugins/platforms/haiku/qhaikurasterwindow.cpp @@ -211,7 +211,7 @@ void HaikuViewProxy::handleKeyEvent(QEvent::Type type, BMessage *message) { int32 key = 0; uint32 code = 0; - const char *bytes = Q_NULLPTR; + const char *bytes = nullptr; QString text; if (message) { @@ -265,7 +265,7 @@ QHaikuRasterWindow::~QHaikuRasterWindow() m_window->UnlockLooper(); delete m_view; - m_view = Q_NULLPTR; + m_view = nullptr; } BView* QHaikuRasterWindow::nativeViewHandle() const diff --git a/src/plugins/platforms/haiku/qhaikurasterwindow.h b/src/plugins/platforms/haiku/qhaikurasterwindow.h index ae57e0f0e4..24b13aa122 100644 --- a/src/plugins/platforms/haiku/qhaikurasterwindow.h +++ b/src/plugins/platforms/haiku/qhaikurasterwindow.h @@ -51,15 +51,15 @@ class HaikuViewProxy : public QObject, public BView Q_OBJECT public: - explicit HaikuViewProxy(BWindow *window, QObject *parent = Q_NULLPTR); + explicit HaikuViewProxy(BWindow *window, QObject *parent = nullptr); - void MessageReceived(BMessage *message) Q_DECL_OVERRIDE; - void Draw(BRect updateRect) Q_DECL_OVERRIDE; - void MouseDown(BPoint pos) Q_DECL_OVERRIDE; - void MouseUp(BPoint pos) Q_DECL_OVERRIDE; - void MouseMoved(BPoint pos, uint32 code, const BMessage *dragMessage) Q_DECL_OVERRIDE; - void KeyDown(const char *bytes, int32 numBytes) Q_DECL_OVERRIDE; - void KeyUp(const char *bytes, int32 numBytes) Q_DECL_OVERRIDE; + void MessageReceived(BMessage *message) override; + void Draw(BRect updateRect) override; + void MouseDown(BPoint pos) override; + void MouseUp(BPoint pos) override; + void MouseMoved(BPoint pos, uint32 code, const BMessage *dragMessage) override; + void KeyDown(const char *bytes, int32 numBytes) override; + void KeyUp(const char *bytes, int32 numBytes) override; Q_SIGNALS: void mouseEvent(const QPoint &localPosition, const QPoint &globalPosition, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Qt::MouseEventSource source); diff --git a/src/plugins/platforms/haiku/qhaikuscreen.cpp b/src/plugins/platforms/haiku/qhaikuscreen.cpp index 54951a0af5..2c8abba8aa 100644 --- a/src/plugins/platforms/haiku/qhaikuscreen.cpp +++ b/src/plugins/platforms/haiku/qhaikuscreen.cpp @@ -58,10 +58,10 @@ QHaikuScreen::QHaikuScreen() QHaikuScreen::~QHaikuScreen() { delete m_cursor; - m_cursor = Q_NULLPTR; + m_cursor = nullptr; delete m_screen; - m_screen = Q_NULLPTR; + m_screen = nullptr; } QPixmap QHaikuScreen::grabWindow(WId winId, int x, int y, int width, int height) const @@ -69,8 +69,8 @@ QPixmap QHaikuScreen::grabWindow(WId winId, int x, int y, int width, int height) if (width == 0 || height == 0) return QPixmap(); - BScreen screen(Q_NULLPTR); - BBitmap *bitmap = Q_NULLPTR; + BScreen screen(nullptr); + BBitmap *bitmap = nullptr; screen.GetBitmap(&bitmap); const BRect frame = (winId ? ((BWindow*)winId)->Frame() : screen.Frame()); diff --git a/src/plugins/platforms/haiku/qhaikuscreen.h b/src/plugins/platforms/haiku/qhaikuscreen.h index 49fa3f0a60..98de6fdd03 100644 --- a/src/plugins/platforms/haiku/qhaikuscreen.h +++ b/src/plugins/platforms/haiku/qhaikuscreen.h @@ -53,13 +53,13 @@ public: QHaikuScreen(); ~QHaikuScreen(); - QPixmap grabWindow(WId window, int x, int y, int width, int height) const Q_DECL_OVERRIDE; + QPixmap grabWindow(WId window, int x, int y, int width, int height) const override; - QRect geometry() const Q_DECL_OVERRIDE; - int depth() const Q_DECL_OVERRIDE; - QImage::Format format() const Q_DECL_OVERRIDE; + QRect geometry() const override; + int depth() const override; + QImage::Format format() const override; - QPlatformCursor *cursor() const Q_DECL_OVERRIDE; + QPlatformCursor *cursor() const override; private: BScreen *m_screen; diff --git a/src/plugins/platforms/haiku/qhaikuservices.h b/src/plugins/platforms/haiku/qhaikuservices.h index a210eb8b61..59ff6395fb 100644 --- a/src/plugins/platforms/haiku/qhaikuservices.h +++ b/src/plugins/platforms/haiku/qhaikuservices.h @@ -47,10 +47,10 @@ QT_BEGIN_NAMESPACE class QHaikuServices : public QPlatformServices { public: - bool openUrl(const QUrl &url) Q_DECL_OVERRIDE; - bool openDocument(const QUrl &url) Q_DECL_OVERRIDE; + bool openUrl(const QUrl &url) override; + bool openDocument(const QUrl &url) override; - QByteArray desktopEnvironment() const Q_DECL_OVERRIDE; + QByteArray desktopEnvironment() const override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/haiku/qhaikuwindow.cpp b/src/plugins/platforms/haiku/qhaikuwindow.cpp index 4634012eda..f8fdf3f92d 100644 --- a/src/plugins/platforms/haiku/qhaikuwindow.cpp +++ b/src/plugins/platforms/haiku/qhaikuwindow.cpp @@ -118,12 +118,12 @@ void HaikuWindowProxy::zoomByQt() QHaikuWindow::QHaikuWindow(QWindow *window) : QPlatformWindow(window) - , m_window(Q_NULLPTR) + , m_window(nullptr) , m_windowState(Qt::WindowNoState) { const QRect rect = initialGeometry(window, window->geometry(), DefaultWindowWidth, DefaultWindowHeight); - HaikuWindowProxy *haikuWindow = new HaikuWindowProxy(window, rect, Q_NULLPTR); + HaikuWindowProxy *haikuWindow = new HaikuWindowProxy(window, rect, nullptr); connect(haikuWindow, SIGNAL(moved(QPoint)), SLOT(haikuWindowMoved(QPoint))); connect(haikuWindow, SIGNAL(resized(QSize,bool)), SLOT(haikuWindowResized(QSize,bool))); connect(haikuWindow, SIGNAL(windowActivated(bool)), SLOT(haikuWindowActivated(bool))); @@ -145,7 +145,7 @@ QHaikuWindow::~QHaikuWindow() m_window->LockLooper(); m_window->Quit(); - m_window = Q_NULLPTR; + m_window = nullptr; } void QHaikuWindow::setGeometry(const QRect &rect) @@ -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())); @@ -335,7 +330,7 @@ void QHaikuWindow::haikuWindowResized(const QSize &size, bool zoomInProgress) void QHaikuWindow::haikuWindowActivated(bool activated) { - QWindowSystemInterface::handleWindowActivated(activated ? window() : Q_NULLPTR); + QWindowSystemInterface::handleWindowActivated(activated ? window() : nullptr); } void QHaikuWindow::haikuWindowMinimized(bool minimize) diff --git a/src/plugins/platforms/haiku/qhaikuwindow.h b/src/plugins/platforms/haiku/qhaikuwindow.h index 75403fb421..bb57742087 100644 --- a/src/plugins/platforms/haiku/qhaikuwindow.h +++ b/src/plugins/platforms/haiku/qhaikuwindow.h @@ -51,14 +51,14 @@ class HaikuWindowProxy : public QObject, public BWindow Q_OBJECT public: - explicit HaikuWindowProxy(QWindow *window, const QRect &rect, QObject *parent = Q_NULLPTR); + explicit HaikuWindowProxy(QWindow *window, const QRect &rect, QObject *parent = nullptr); - void FrameMoved(BPoint pos) Q_DECL_OVERRIDE; - void FrameResized(float width, float height) Q_DECL_OVERRIDE; - void WindowActivated(bool activated) Q_DECL_OVERRIDE; - void Minimize(bool minimize) Q_DECL_OVERRIDE; - void Zoom(BPoint pos, float width, float height) Q_DECL_OVERRIDE; - bool QuitRequested() Q_DECL_OVERRIDE; + void FrameMoved(BPoint pos) override; + void FrameResized(float width, float height) override; + void WindowActivated(bool activated) override; + void Minimize(bool minimize) override; + void Zoom(BPoint pos, float width, float height) override; + bool QuitRequested() override; void zoomByQt(); @@ -83,23 +83,23 @@ public: explicit QHaikuWindow(QWindow *window); virtual ~QHaikuWindow(); - void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; - QMargins frameMargins() const Q_DECL_OVERRIDE; - void setVisible(bool visible) Q_DECL_OVERRIDE; + void setGeometry(const QRect &rect) override; + QMargins frameMargins() const override; + void setVisible(bool visible) override; - bool isExposed() const Q_DECL_OVERRIDE; - bool isActive() const Q_DECL_OVERRIDE; + bool isExposed() const override; + bool isActive() const override; - WId winId() const Q_DECL_OVERRIDE; + WId winId() const override; BWindow* nativeHandle() const; - void requestActivateWindow() Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; - void setWindowFlags(Qt::WindowFlags flags) Q_DECL_OVERRIDE; + void requestActivateWindow() override; + void setWindowState(Qt::WindowStates state) override; + void setWindowFlags(Qt::WindowFlags flags) override; - void setWindowTitle(const QString &title) Q_DECL_OVERRIDE; + void setWindowTitle(const QString &title) override; - void propagateSizeHints() Q_DECL_OVERRIDE; + void propagateSizeHints() override; protected: HaikuWindowProxy *m_window; @@ -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/integrity/main.cpp b/src/plugins/platforms/integrity/main.cpp index f75e227335..6313aa47e5 100644 --- a/src/plugins/platforms/integrity/main.cpp +++ b/src/plugins/platforms/integrity/main.cpp @@ -47,7 +47,7 @@ class QIntegrityFbIntegrationPlugin : public QPlatformIntegrationPlugin Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "integrity.json") public: - QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; + QPlatformIntegration *create(const QString&, const QStringList&) override; }; QPlatformIntegration* QIntegrityFbIntegrationPlugin::create(const QString& system, const QStringList& paramList) diff --git a/src/plugins/platforms/integrity/qintegrityfbintegration.h b/src/plugins/platforms/integrity/qintegrityfbintegration.h index a954dc2356..d0cd5417ab 100644 --- a/src/plugins/platforms/integrity/qintegrityfbintegration.h +++ b/src/plugins/platforms/integrity/qintegrityfbintegration.h @@ -54,19 +54,19 @@ public: QIntegrityFbIntegration(const QStringList ¶mList); ~QIntegrityFbIntegration(); - void initialize() Q_DECL_OVERRIDE; - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; + void initialize() override; + bool hasCapability(QPlatformIntegration::Capability cap) const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QAbstractEventDispatcher *createEventDispatcher() const override; - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; - QPlatformServices *services() const Q_DECL_OVERRIDE; - QPlatformInputContext *inputContext() const Q_DECL_OVERRIDE { return m_inputContext; } + QPlatformFontDatabase *fontDatabase() const override; + QPlatformServices *services() const override; + QPlatformInputContext *inputContext() const override { return m_inputContext; } - QPlatformNativeInterface *nativeInterface() const Q_DECL_OVERRIDE; + QPlatformNativeInterface *nativeInterface() const override; QList<QPlatformScreen *> screens() const; diff --git a/src/plugins/platforms/integrity/qintegrityfbscreen.cpp b/src/plugins/platforms/integrity/qintegrityfbscreen.cpp index 3979937955..d64b96ca4c 100644 --- a/src/plugins/platforms/integrity/qintegrityfbscreen.cpp +++ b/src/plugins/platforms/integrity/qintegrityfbscreen.cpp @@ -204,15 +204,14 @@ QRegion QIntegrityFbScreen::doRedraw() if (!mBlitter) mBlitter = new QPainter(&mFbScreenImage); - QVector<QRect> rects = touched.rects(); - for (int i = 0; i < rects.size(); i++) { + for (QRect rect : touched) { FBRect fbrect = { - (uint32_t)rects[i].left(), - (uint32_t)rects[i].top(), - (uint32_t)rects[i].width(), - (uint32_t)rects[i].height() + (uint32_t)rect.left(), + (uint32_t)rect.top(), + (uint32_t)rect.width(), + (uint32_t)rect.height() }; - mBlitter->drawImage(rects[i], mScreenImage, rects[i]); + mBlitter->drawImage(rect, mScreenImage, rect); gh_FB_expose(mFbh, &fbrect, NULL); } return touched; diff --git a/src/plugins/platforms/integrity/qintegrityfbscreen.h b/src/plugins/platforms/integrity/qintegrityfbscreen.h index 6bc78913c9..c38b4f073d 100644 --- a/src/plugins/platforms/integrity/qintegrityfbscreen.h +++ b/src/plugins/platforms/integrity/qintegrityfbscreen.h @@ -57,9 +57,9 @@ public: bool initialize(); - QPixmap grabWindow(WId wid, int x, int y, int width, int height) const Q_DECL_OVERRIDE; + QPixmap grabWindow(WId wid, int x, int y, int width, int height) const override; - QRegion doRedraw() Q_DECL_OVERRIDE; + QRegion doRedraw() override; private: QStringList mArgs; diff --git a/src/plugins/platforms/integrity/qintegrityhidmanager.cpp b/src/plugins/platforms/integrity/qintegrityhidmanager.cpp index 49583735f5..3570b90134 100644 --- a/src/plugins/platforms/integrity/qintegrityhidmanager.cpp +++ b/src/plugins/platforms/integrity/qintegrityhidmanager.cpp @@ -73,8 +73,8 @@ public: { CheckSuccess(gh_hid_close(handle)); }; - void process_event(void) Q_DECL_OVERRIDE; - void async_wait(void) Q_DECL_OVERRIDE; + void process_event(void) override; + void async_wait(void) override; HIDDriver *get_driver(void) { return driver; }; HIDHandle get_handle(void) { return handle; }; private: @@ -92,8 +92,8 @@ public: { qDeleteAll(devices); }; - void process_event(void) Q_DECL_OVERRIDE; - void async_wait(void) Q_DECL_OVERRIDE; + void process_event(void) override; + void async_wait(void) override; void find_devices(void); private: QHash<Value, HIDDeviceHandler *> devices; diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm index bea2897240..54152aebf7 100644 --- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm +++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm @@ -302,7 +302,7 @@ public: g_iteratorCurrentUrl.setLocalData(QString()); } - QString next() Q_DECL_OVERRIDE + QString next() override { // Cache the URL that we are about to return, since QDir will immediately create a // new file engine on the file and ask if it exists. Unless we do this, we end up @@ -314,17 +314,17 @@ public: return url; } - bool hasNext() const Q_DECL_OVERRIDE + bool hasNext() const override { return m_enumerator->hasNext(); } - QString currentFileName() const Q_DECL_OVERRIDE + QString currentFileName() const override { return g_iteratorCurrentUrl.localData(); } - QFileInfo currentFileInfo() const Q_DECL_OVERRIDE + QFileInfo currentFileInfo() const override { return QFileInfo(currentFileName()); } diff --git a/src/plugins/platforms/ios/qiosbackingstore.h b/src/plugins/platforms/ios/qiosbackingstore.h index 3954347471..38006ba90b 100644 --- a/src/plugins/platforms/ios/qiosbackingstore.h +++ b/src/plugins/platforms/ios/qiosbackingstore.h @@ -54,10 +54,7 @@ public: QIOSBackingStore(QWindow *window); ~QIOSBackingStore(); - void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - -private: - QOpenGLContext *m_context; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; }; 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.h b/src/plugins/platforms/ios/qiosclipboard.h index f3ccfcace0..3fe9b29b71 100644 --- a/src/plugins/platforms/ios/qiosclipboard.h +++ b/src/plugins/platforms/ios/qiosclipboard.h @@ -58,10 +58,10 @@ public: QIOSClipboard(); ~QIOSClipboard(); - QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE; - void setMimeData(QMimeData *mimeData, QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE; - bool supportsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE; - bool ownsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE; + QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; + void setMimeData(QMimeData *mimeData, QClipboard::Mode mode = QClipboard::Clipboard) override; + bool supportsMode(QClipboard::Mode mode) const override; + bool ownsMode(QClipboard::Mode mode) const override; private: QUIClipboard *m_clipboard; diff --git a/src/plugins/platforms/ios/qiosclipboard.mm b/src/plugins/platforms/ios/qiosclipboard.mm index 6a585c9052..9a975eadc9 100644 --- a/src/plugins/platforms/ios/qiosclipboard.mm +++ b/src/plugins/platforms/ios/qiosclipboard.mm @@ -138,8 +138,8 @@ public: QIOSMimeData(QClipboard::Mode mode) : QMimeData(), m_mode(mode) { } ~QIOSMimeData() { } - QStringList formats() const Q_DECL_OVERRIDE; - QVariant retrieveData(const QString &mimeType, QVariant::Type type) const Q_DECL_OVERRIDE; + QStringList formats() const override; + QVariant retrieveData(const QString &mimeType, QVariant::Type type) const override; private: const QClipboard::Mode m_mode; @@ -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/qioscontext.h b/src/plugins/platforms/ios/qioscontext.h index ce50eff1d9..a2595877dc 100644 --- a/src/plugins/platforms/ios/qioscontext.h +++ b/src/plugins/platforms/ios/qioscontext.h @@ -57,18 +57,18 @@ public: QIOSContext(QOpenGLContext *context); ~QIOSContext(); - QSurfaceFormat format() const Q_DECL_OVERRIDE; + QSurfaceFormat format() const override; - void swapBuffers(QPlatformSurface *surface) Q_DECL_OVERRIDE; + void swapBuffers(QPlatformSurface *surface) override; - bool makeCurrent(QPlatformSurface *surface) Q_DECL_OVERRIDE; - void doneCurrent() Q_DECL_OVERRIDE; + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; - GLuint defaultFramebufferObject(QPlatformSurface *) const Q_DECL_OVERRIDE; - QFunctionPointer getProcAddress(const char *procName) Q_DECL_OVERRIDE; + GLuint defaultFramebufferObject(QPlatformSurface *) const override; + QFunctionPointer getProcAddress(const char *procName) override; - bool isSharing() const Q_DECL_OVERRIDE; - bool isValid() const Q_DECL_OVERRIDE; + bool isSharing() const override; + bool isValid() const override; private Q_SLOTS: void windowDestroyed(QObject *object); diff --git a/src/plugins/platforms/ios/qioseventdispatcher.h b/src/plugins/platforms/ios/qioseventdispatcher.h index c1442ed1e8..62133b9510 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.h +++ b/src/plugins/platforms/ios/qioseventdispatcher.h @@ -51,8 +51,8 @@ class QIOSEventDispatcher : public QEventDispatcherCoreFoundation public: explicit QIOSEventDispatcher(QObject *parent = 0); - bool processEvents(QEventLoop::ProcessEventsFlags flags) Q_DECL_OVERRIDE; - bool processPostedEvents() Q_DECL_OVERRIDE; + bool processEvents(QEventLoop::ProcessEventsFlags flags) override; + bool processPostedEvents() override; void handleRunLoopExit(CFRunLoopActivity activity); diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm index 7194d5bed1..a6f6a7aac9 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.mm +++ b/src/plugins/platforms/ios/qioseventdispatcher.mm @@ -228,7 +228,7 @@ extern "C" int qt_main_wrapper(int argc, char *argv[]) } } - qEventDispatcherDebug() << "Running UIApplicationMain"; qIndent(); + qCDebug(lcEventDispatcher) << "Running UIApplicationMain"; return UIApplicationMain(argc, argv, nil, NSStringFromClass([QIOSApplicationDelegate class])); } } @@ -263,7 +263,7 @@ static void __attribute__((noinline, noreturn)) user_main_trampoline() int exitCode = main(argc, argv); delete[] argv; - qEventDispatcherDebug() << "Returned from main with exit code " << exitCode; + qCDebug(lcEventDispatcher) << "Returned from main with exit code " << exitCode; if (Q_UNLIKELY(debugStackUsage)) userMainStack.printUsage(); @@ -322,7 +322,7 @@ static bool rootLevelRunLoopIntegration() + (void)applicationDidFinishLaunching:(NSNotification *)notification { - qCDebug(lcQpaApplication) << "Application launched with options" << notification.userInfo; + qCDebug(lcEventDispatcher) << "Application launched with options" << notification.userInfo; if (!isQtApplication()) return; @@ -331,7 +331,7 @@ static bool rootLevelRunLoopIntegration() // We schedule the main-redirection for the next run-loop pass, so that we // can return from this function and let UIApplicationMain finish its job. // This results in running Qt's application eventloop as a nested runloop. - qCDebug(lcQpaApplication) << "Scheduling main() on next run-loop pass"; + qCDebug(lcEventDispatcher) << "Scheduling main() on next run-loop pass"; CFRunLoopTimerRef userMainTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef) { user_main_trampoline(); }); CFRunLoopAddTimer(CFRunLoopGetMain(), userMainTimer, kCFRunLoopCommonModes); @@ -339,10 +339,10 @@ static bool rootLevelRunLoopIntegration() return; } + switch (setjmp(processEventEnterJumpPoint)) { case kJumpPointSetSuccessfully: - qCDebug(lcQpaApplication) << "Running main() on separate stack"; qIndent(); - + qCDebug(lcEventDispatcher) << "Running main() on separate stack"; // Redirect the stack pointer to the start of the reserved stack. This ensures // that when we longjmp out of the event dispatcher and continue execution, the // 'Qt main' call-stack will not be smashed, as it lives in a part of the stack @@ -360,7 +360,7 @@ static bool rootLevelRunLoopIntegration() case kJumpedFromEventDispatcherProcessEvents: // We've returned from the longjmp in the event dispatcher, // and the stack has been restored to its old self. - qUnIndent(); qCDebug(lcQpaApplication) << "Returned from processEvents"; + qCDebug(lcEventDispatcher) << "↳ Jumped from processEvents due to exec"; if (Q_UNLIKELY(debugStackUsage)) userMainStack.printUsage(); @@ -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 { @@ -396,18 +396,18 @@ static const char kApplicationWillTerminateExitCode = SIGTERM | 0x80; applicationAboutToTerminate = true; switch (setjmp(applicationWillTerminateJumpPoint)) { case kJumpPointSetSuccessfully: - qEventDispatcherDebug() << "Exiting qApp with SIGTERM exit code"; qIndent(); + qCDebug(lcEventDispatcher) << "Exiting qApp with SIGTERM exit code"; qApp->exit(kApplicationWillTerminateExitCode); // The runloop will not exit when the application is about to terminate, // so we'll never see the exit activity and have a chance to return from // QEventLoop::exec(). We initiate the return manually as a workaround. - qEventDispatcherDebug() << "Manually triggering return from event loop exec"; + qCDebug(lcEventDispatcher) << "Manually triggering return from event loop exec"; static_cast<QIOSEventDispatcher *>(qApp->eventDispatcher())->interruptEventLoopExec(); break; case kJumpedFromUserMainTrampoline: // The user's main has returned, so we're ready to let iOS terminate the application - qUnIndent(); qEventDispatcherDebug() << "kJumpedFromUserMainTrampoline, allowing iOS to terminate"; + qCDebug(lcEventDispatcher) << "kJumpedFromUserMainTrampoline, allowing iOS to terminate"; break; default: qFatal("Unexpected jump result in event loop integration"); @@ -434,13 +434,15 @@ bool __attribute__((returns_twice)) QIOSEventDispatcher::processEvents(QEventLoo return QEventDispatcherCoreFoundation::processEvents(flags); if (applicationAboutToTerminate) { - qEventDispatcherDebug() << "Detected QEventLoop exec after application termination"; + qCDebug(lcEventDispatcher) << "Detected QEventLoop exec after application termination"; // Re-issue exit, and return immediately qApp->exit(kApplicationWillTerminateExitCode); return false; } if (!m_processEventLevel && (flags & QEventLoop::EventLoopExec)) { + qCDebug(lcEventDispatcher) << "Processing events with flags" << flags; + ++m_processEventLevel; m_runLoopExitObserver.addToMode(kCFRunLoopCommonModes); @@ -449,7 +451,7 @@ bool __attribute__((returns_twice)) QIOSEventDispatcher::processEvents(QEventLoo // is asked to exit, so that we can return from QEventLoop::exec(). switch (setjmp(processEventExitJumpPoint)) { case kJumpPointSetSuccessfully: - qEventDispatcherDebug() << "QEventLoop exec detected, jumping back to native runloop"; + qCDebug(lcEventDispatcher) << "QEventLoop exec detected, jumping back to system runloop ↵"; longjmp(processEventEnterJumpPoint, kJumpedFromEventDispatcherProcessEvents); break; case kJumpedFromEventLoopExecInterrupt: @@ -457,7 +459,7 @@ bool __attribute__((returns_twice)) QIOSEventDispatcher::processEvents(QEventLoo // signal), and we jumped back though processEventExitJumpPoint. We return from processEvents, // which will emit aboutToQuit if it's QApplication's event loop, and then return to the user's // main, which can do whatever it wants, including calling exec() on the application again. - qEventDispatcherDebug() << "kJumpedFromEventLoopExecInterrupt, returning with eventsProcessed = true"; + qCDebug(lcEventDispatcher) << "⇢ System runloop exited, returning with eventsProcessed = true"; return true; default: qFatal("Unexpected jump result in event loop integration"); @@ -485,9 +487,8 @@ bool QIOSEventDispatcher::processPostedEvents() if (!QEventDispatcherCoreFoundation::processPostedEvents()) return false; - qEventDispatcherDebug() << "Sending window system events for " << m_processEvents.flags; qIndent(); + qCDebug(lcEventDispatcher) << "Sending window system events for" << m_processEvents.flags; QWindowSystemInterface::sendWindowSystemEvents(m_processEvents.flags); - qUnIndent(); return true; } @@ -497,10 +498,8 @@ void QIOSEventDispatcher::handleRunLoopExit(CFRunLoopActivity activity) Q_UNUSED(activity); Q_ASSERT(activity == kCFRunLoopExit); - if (m_processEventLevel == 1 && !currentEventLoop()->isRunning()) { - qEventDispatcherDebug() << "Root runloop level exited"; + if (m_processEventLevel == 1 && !currentEventLoop()->isRunning()) interruptEventLoopExec(); - } } void QIOSEventDispatcher::interruptEventLoopExec() @@ -516,12 +515,12 @@ void QIOSEventDispatcher::interruptEventLoopExec() // processEvents, instead of back in didFinishLaunchingWithOptions. switch (setjmp(processEventEnterJumpPoint)) { case kJumpPointSetSuccessfully: - qEventDispatcherDebug() << "Jumping back to processEvents"; + qCDebug(lcEventDispatcher) << "Jumping into processEvents due to system runloop exit ⇢"; longjmp(processEventExitJumpPoint, kJumpedFromEventLoopExecInterrupt); break; case kJumpedFromEventDispatcherProcessEvents: // QEventLoop was re-executed - qEventDispatcherDebug() << "kJumpedFromEventDispatcherProcessEvents"; + qCDebug(lcEventDispatcher) << "↳ Jumped from processEvents due to re-exec"; break; default: qFatal("Unexpected jump result in event loop integration"); diff --git a/src/plugins/platforms/ios/qiosfiledialog.h b/src/plugins/platforms/ios/qiosfiledialog.h index 0b56bd20bf..5cb1b45e20 100644 --- a/src/plugins/platforms/ios/qiosfiledialog.h +++ b/src/plugins/platforms/ios/qiosfiledialog.h @@ -53,17 +53,17 @@ public: QIOSFileDialog(); ~QIOSFileDialog(); - void exec() Q_DECL_OVERRIDE; - bool defaultNameFilterDisables() const Q_DECL_OVERRIDE { return false; } - bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) Q_DECL_OVERRIDE; - void hide() Q_DECL_OVERRIDE; - void setDirectory(const QUrl &) Q_DECL_OVERRIDE {} - QUrl directory() const Q_DECL_OVERRIDE { return QUrl(); } - void selectFile(const QUrl &) Q_DECL_OVERRIDE {} - QList<QUrl> selectedFiles() const Q_DECL_OVERRIDE; - void setFilter() Q_DECL_OVERRIDE {} - void selectNameFilter(const QString &) Q_DECL_OVERRIDE {} - QString selectedNameFilter() const Q_DECL_OVERRIDE { return QString(); } + void exec() override; + bool defaultNameFilterDisables() const override { return false; } + bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; + void hide() override; + void setDirectory(const QUrl &) override {} + QUrl directory() const override { return QUrl(); } + void selectFile(const QUrl &) override {} + QList<QUrl> selectedFiles() const override; + void setFilter() override {} + void selectNameFilter(const QString &) override {} + QString selectedNameFilter() const override { return QString(); } void selectedFilesChanged(QList<QUrl> selection); diff --git a/src/plugins/platforms/ios/qiosfiledialog.mm b/src/plugins/platforms/ios/qiosfiledialog.mm index c5722d33f8..5987bc1540 100644 --- a/src/plugins/platforms/ios/qiosfiledialog.mm +++ b/src/plugins/platforms/ios/qiosfiledialog.mm @@ -48,7 +48,7 @@ #include "qiosoptionalplugininterface.h" QIOSFileDialog::QIOSFileDialog() - : m_viewController(Q_NULLPTR) + : m_viewController(nullptr) { } @@ -112,7 +112,7 @@ void QIOSFileDialog::hide() [m_viewController dismissViewControllerAnimated:YES completion:nil]; [m_viewController release]; - m_viewController = Q_NULLPTR; + m_viewController = nullptr; m_eventLoop.exit(); } diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h index 966d1a7e80..255cf8bca9 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.h +++ b/src/plugins/platforms/ios/qiosinputcontext.h @@ -88,24 +88,24 @@ public: QIOSInputContext(); ~QIOSInputContext(); - bool isValid() const Q_DECL_OVERRIDE { return true; } + bool isValid() const override { return true; } - void showInputPanel() Q_DECL_OVERRIDE; - void hideInputPanel() Q_DECL_OVERRIDE; + void showInputPanel() override; + void hideInputPanel() override; - bool isInputPanelVisible() const Q_DECL_OVERRIDE; - bool isAnimating() const Q_DECL_OVERRIDE; - QRectF keyboardRect() const Q_DECL_OVERRIDE; + bool isInputPanelVisible() const override; + bool isAnimating() const override; + QRectF keyboardRect() const override; - void update(Qt::InputMethodQueries) Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void commit() Q_DECL_OVERRIDE; + void update(Qt::InputMethodQueries) override; + void reset() override; + void commit() override; - QLocale locale() const Q_DECL_OVERRIDE; + QLocale locale() const override; void clearCurrentFocusObject(); - void setFocusObject(QObject *object) Q_DECL_OVERRIDE; + void setFocusObject(QObject *object) override; void focusWindowChanged(QWindow *focusWindow); void scrollToCursor(); diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h index 1d53734096..6be8855020 100644 --- a/src/plugins/platforms/ios/qiosintegration.h +++ b/src/plugins/platforms/ios/qiosintegration.h @@ -58,54 +58,49 @@ class QIOSServices; class QIOSIntegration : public QPlatformNativeInterface, public QPlatformIntegration { Q_OBJECT - Q_PROPERTY(bool debugWindowManagement READ debugWindowManagement WRITE setDebugWindowManagement); - public: QIOSIntegration(); ~QIOSIntegration(); - bool hasCapability(Capability cap) const Q_DECL_OVERRIDE; + bool hasCapability(Capability cap) const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; - QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; - QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const Q_DECL_OVERRIDE; + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; + QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; + QPlatformFontDatabase *fontDatabase() const override; #ifndef QT_NO_CLIPBOARD - QPlatformClipboard *clipboard() const Q_DECL_OVERRIDE; + QPlatformClipboard *clipboard() const override; #endif - QPlatformInputContext *inputContext() const Q_DECL_OVERRIDE; - QPlatformServices *services() const Q_DECL_OVERRIDE; + QPlatformInputContext *inputContext() const override; + QPlatformServices *services() const override; - QVariant styleHint(StyleHint hint) const Q_DECL_OVERRIDE; + QVariant styleHint(StyleHint hint) const override; - QStringList themeNames() const Q_DECL_OVERRIDE; - QPlatformTheme *createPlatformTheme(const QString &name) const Q_DECL_OVERRIDE; + QStringList themeNames() const override; + QPlatformTheme *createPlatformTheme(const QString &name) const override; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; - QPlatformNativeInterface *nativeInterface() const Q_DECL_OVERRIDE; + QAbstractEventDispatcher *createEventDispatcher() const override; + QPlatformNativeInterface *nativeInterface() const override; QTouchDevice *touchDevice(); #ifndef QT_NO_ACCESSIBILITY - QPlatformAccessibility *accessibility() const Q_DECL_OVERRIDE; + QPlatformAccessibility *accessibility() const override; #endif // Called from Objective-C class QIOSScreenTracker, which can't be friended void addScreen(QPlatformScreen *screen) { screenAdded(screen); } void destroyScreen(QPlatformScreen *screen) { QPlatformIntegration::destroyScreen(screen); } - void beep() const Q_DECL_OVERRIDE; + void beep() const override; static QIOSIntegration *instance(); // -- QPlatformNativeInterface -- - void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) Q_DECL_OVERRIDE; - - void setDebugWindowManagement(bool); - bool debugWindowManagement() const; + void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; QFactoryLoader *optionalPlugins() { return m_optionalPlugins; } @@ -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..92c1e39d72 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,12 +112,15 @@ 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; } m_touchDevice->setCapabilities(touchCapabilities); QWindowSystemInterface::registerTouchDevice(m_touchDevice); +#if QT_CONFIG(tabletevent) + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); +#endif QMacInternalPasteboardMime::initializeMimeTypes(); for (int i = 0; i < m_optionalPlugins->metaData().size(); ++i) @@ -201,12 +199,12 @@ class QIOSOffscreenSurface : public QPlatformOffscreenSurface public: QIOSOffscreenSurface(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) {} - QSurfaceFormat format() const Q_DECL_OVERRIDE + QSurfaceFormat format() const override { Q_ASSERT(offscreenSurface()); return offscreenSurface()->requestedFormat(); } - bool isValid() const Q_DECL_OVERRIDE { return true; } + bool isValid() const override { return true; } }; QPlatformOffscreenSurface *QIOSIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const @@ -322,16 +320,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..32022a3bb8 100644 --- a/src/plugins/platforms/ios/qiosmenu.h +++ b/src/plugins/platforms/ios/qiosmenu.h @@ -56,25 +56,21 @@ 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; - void setVisible(bool isVisible) Q_DECL_OVERRIDE; - void setIsSeparator(bool) Q_DECL_OVERRIDE; - void setFont(const QFont &) Q_DECL_OVERRIDE {} - void setRole(MenuRole role) Q_DECL_OVERRIDE; - void setCheckable(bool) Q_DECL_OVERRIDE {} - void setChecked(bool) Q_DECL_OVERRIDE {} + void setText(const QString &text) override; + void setIcon(const QIcon &) override {} + void setMenu(QPlatformMenu *) override; + void setVisible(bool isVisible) override; + void setIsSeparator(bool) override; + void setFont(const QFont &) override {} + void setRole(MenuRole role) override; + void setCheckable(bool) override {} + void setChecked(bool) override {} #ifndef QT_NO_SHORTCUT - void setShortcut(const QKeySequence&) Q_DECL_OVERRIDE; + void setShortcut(const QKeySequence&) override; #endif - void setEnabled(bool enabled) Q_DECL_OVERRIDE; - void setIconSize(int) Q_DECL_OVERRIDE {} + void setEnabled(bool enabled) override; + void setIconSize(int) override {} - quintptr m_tag; bool m_visible; QString m_text; MenuRole m_role; @@ -92,25 +88,22 @@ public: QIOSMenu(); ~QIOSMenu(); - void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE; - void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; - void syncMenuItem(QPlatformMenuItem *) Q_DECL_OVERRIDE; - void syncSeparatorsCollapsible(bool) Q_DECL_OVERRIDE {} + void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; + void removeMenuItem(QPlatformMenuItem *menuItem) override; + void syncMenuItem(QPlatformMenuItem *) override; + void syncSeparatorsCollapsible(bool) override {} - void setTag(quintptr tag) Q_DECL_OVERRIDE; - quintptr tag()const Q_DECL_OVERRIDE; + void setText(const QString &) override; + void setIcon(const QIcon &) override {} + void setEnabled(bool enabled) override; + void setVisible(bool visible) override; + void setMenuType(MenuType type) override; - void setText(const QString &) Q_DECL_OVERRIDE; - void setIcon(const QIcon &) Q_DECL_OVERRIDE {} - void setEnabled(bool enabled) Q_DECL_OVERRIDE; - void setVisible(bool visible) Q_DECL_OVERRIDE; - void setMenuType(MenuType type) Q_DECL_OVERRIDE; + void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) override; + void dismiss() override; - void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) Q_DECL_OVERRIDE; - void dismiss() Q_DECL_OVERRIDE; - - QPlatformMenuItem *menuItemAt(int position) const Q_DECL_OVERRIDE; - QPlatformMenuItem *menuItemForTag(quintptr tag) const Q_DECL_OVERRIDE; + QPlatformMenuItem *menuItemAt(int position) const override; + QPlatformMenuItem *menuItemForTag(quintptr tag) const override; void handleItemSelected(QIOSMenuItem *menuItem); @@ -118,10 +111,9 @@ public: static id menuActionTarget() { return m_currentMenu ? m_currentMenu->m_menuController : 0; } protected: - bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *obj, QEvent *event) 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.h b/src/plugins/platforms/ios/qiosmessagedialog.h index e67e10a5e1..92a4db8319 100644 --- a/src/plugins/platforms/ios/qiosmessagedialog.h +++ b/src/plugins/platforms/ios/qiosmessagedialog.h @@ -54,9 +54,9 @@ public: QIOSMessageDialog(); ~QIOSMessageDialog(); - void exec() Q_DECL_OVERRIDE; - bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) Q_DECL_OVERRIDE; - void hide() Q_DECL_OVERRIDE; + void exec() override; + bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; + void hide() override; private: QEventLoop m_eventLoop; diff --git a/src/plugins/platforms/ios/qiosmessagedialog.mm b/src/plugins/platforms/ios/qiosmessagedialog.mm index 4f0c667861..9d05b792c2 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> @@ -49,7 +48,7 @@ #include "qiosmessagedialog.h" QIOSMessageDialog::QIOSMessageDialog() - : m_alertController(Q_NULLPTR) + : m_alertController(nullptr) { } @@ -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 @@ -138,5 +136,5 @@ void QIOSMessageDialog::hide() m_eventLoop.exit(); [m_alertController dismissViewControllerAnimated:YES completion:nil]; [m_alertController release]; - m_alertController = Q_NULLPTR; + m_alertController = nullptr; } diff --git a/src/plugins/platforms/ios/qiosoptionalplugininterface.h b/src/plugins/platforms/ios/qiosoptionalplugininterface.h index 3f74e41c83..660c74e856 100644 --- a/src/plugins/platforms/ios/qiosoptionalplugininterface.h +++ b/src/plugins/platforms/ios/qiosoptionalplugininterface.h @@ -55,7 +55,7 @@ class QIosOptionalPluginInterface public: virtual ~QIosOptionalPluginInterface() {} virtual void initPlugin() const {}; - virtual UIViewController* createImagePickerController(QIOSFileDialog *) const { return Q_NULLPTR; }; + virtual UIViewController* createImagePickerController(QIOSFileDialog *) const { return nullptr; }; }; Q_DECLARE_INTERFACE(QIosOptionalPluginInterface, QIosOptionalPluginInterface_iid) diff --git a/src/plugins/platforms/ios/qiosscreen.h b/src/plugins/platforms/ios/qiosscreen.h index be0f301710..d5a253461d 100644 --- a/src/plugins/platforms/ios/qiosscreen.h +++ b/src/plugins/platforms/ios/qiosscreen.h @@ -62,18 +62,18 @@ public: QString name() const override; - QRect geometry() const Q_DECL_OVERRIDE; - QRect availableGeometry() const Q_DECL_OVERRIDE; - int depth() const Q_DECL_OVERRIDE; - QImage::Format format() const Q_DECL_OVERRIDE; - QSizeF physicalSize() const Q_DECL_OVERRIDE; - QDpi logicalDpi() const Q_DECL_OVERRIDE; - qreal devicePixelRatio() const Q_DECL_OVERRIDE; + QRect geometry() const override; + QRect availableGeometry() const override; + int depth() const override; + QImage::Format format() const override; + QSizeF physicalSize() const override; + QDpi logicalDpi() const override; + qreal devicePixelRatio() const override; qreal refreshRate() const override; - Qt::ScreenOrientation nativeOrientation() const Q_DECL_OVERRIDE; - Qt::ScreenOrientation orientation() const Q_DECL_OVERRIDE; - void setOrientationUpdateMask(Qt::ScreenOrientations mask) Q_DECL_OVERRIDE; + Qt::ScreenOrientation nativeOrientation() const override; + Qt::ScreenOrientation orientation() const override; + void setOrientationUpdateMask(Qt::ScreenOrientations mask) override; QPixmap grabWindow(WId window, int x, int y, int width, int height) const override; 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..fe3c29d037 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 @@ -617,7 +608,7 @@ static void executeBlockWithoutAnimation(Block block) - (QIOSLoupeLayer *)createLoupeLayer { Q_UNREACHABLE(); - return Q_NULLPTR; + return nullptr; } - (void)updateFocalPoint:(QPointF)touchPoint @@ -993,12 +984,12 @@ static void executeBlockWithoutAnimation(Block block) QT_BEGIN_NAMESPACE -QIOSEditMenu *QIOSTextInputOverlay::s_editMenu = Q_NULLPTR; +QIOSEditMenu *QIOSTextInputOverlay::s_editMenu = nullptr; QIOSTextInputOverlay::QIOSTextInputOverlay() - : m_cursorRecognizer(Q_NULLPTR) - , m_selectionRecognizer(Q_NULLPTR) - , m_openMenuOnTapRecognizer(Q_NULLPTR) + : m_cursorRecognizer(nullptr) + , m_selectionRecognizer(nullptr) + , m_openMenuOnTapRecognizer(nullptr) { connect(qApp, &QGuiApplication::focusObjectChanged, this, &QIOSTextInputOverlay::updateFocusObject); } @@ -1021,10 +1012,10 @@ void QIOSTextInputOverlay::updateFocusObject() [m_selectionRecognizer release]; [m_openMenuOnTapRecognizer release]; [s_editMenu release]; - m_cursorRecognizer = Q_NULLPTR; - m_selectionRecognizer = Q_NULLPTR; - m_openMenuOnTapRecognizer = Q_NULLPTR; - s_editMenu = Q_NULLPTR; + m_cursorRecognizer = nullptr; + m_selectionRecognizer = nullptr; + m_openMenuOnTapRecognizer = nullptr; + s_editMenu = nullptr; } if (platformInputContext()->inputMethodAccepted()) { 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/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h index fc6b58178a..c917679a91 100644 --- a/src/plugins/platforms/ios/qiostheme.h +++ b/src/plugins/platforms/ios/qiostheme.h @@ -52,16 +52,16 @@ public: QIOSTheme(); ~QIOSTheme(); - const QPalette *palette(Palette type = SystemPalette) const Q_DECL_OVERRIDE; - QVariant themeHint(ThemeHint hint) const Q_DECL_OVERRIDE; + const QPalette *palette(Palette type = SystemPalette) const override; + QVariant themeHint(ThemeHint hint) const override; - QPlatformMenuItem* createPlatformMenuItem() const Q_DECL_OVERRIDE; - QPlatformMenu* createPlatformMenu() const Q_DECL_OVERRIDE; + QPlatformMenuItem* createPlatformMenuItem() const override; + QPlatformMenu* createPlatformMenu() const override; - bool usePlatformNativeDialog(DialogType type) const Q_DECL_OVERRIDE; - QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const Q_DECL_OVERRIDE; + bool usePlatformNativeDialog(DialogType type) const override; + QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; - const QFont *font(Font type = SystemFont) const Q_DECL_OVERRIDE; + const QFont *font(Font type = SystemFont) const override; static const char *name; 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..d5af31044d 100644 --- a/src/plugins/platforms/ios/qioswindow.h +++ b/src/plugins/platforms/ios/qioswindow.h @@ -60,37 +60,37 @@ public: explicit QIOSWindow(QWindow *window); ~QIOSWindow(); - void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; + void setGeometry(const QRect &rect) override; - void setWindowState(Qt::WindowState 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; - void setOpacity(qreal level) Q_DECL_OVERRIDE; + void setWindowState(Qt::WindowStates state) override; + void setParent(const QPlatformWindow *window) override; + void handleContentOrientationChange(Qt::ScreenOrientation orientation) override; + void setVisible(bool visible) override; + void setOpacity(qreal level) override; - bool isExposed() const Q_DECL_OVERRIDE; - void propagateSizeHints() Q_DECL_OVERRIDE {} + bool isExposed() const override; + void propagateSizeHints() override {} QMargins safeAreaMargins() const override; - void raise() Q_DECL_OVERRIDE{ raiseOrLower(true); } - void lower() Q_DECL_OVERRIDE { raiseOrLower(false); } + void raise() override{ raiseOrLower(true); } + void lower() override { raiseOrLower(false); } bool shouldAutoActivateWindow() const; - void requestActivateWindow() Q_DECL_OVERRIDE; + void requestActivateWindow() override; - qreal devicePixelRatio() const Q_DECL_OVERRIDE; + qreal devicePixelRatio() const override; - bool setMouseGrabEnabled(bool grab) Q_DECL_OVERRIDE { return grab; } - bool setKeyboardGrabEnabled(bool grab) Q_DECL_OVERRIDE { return grab; } + bool setMouseGrabEnabled(bool grab) override { return grab; } + bool setKeyboardGrabEnabled(bool grab) override { return grab; } - WId winId() const Q_DECL_OVERRIDE { return WId(m_view); } + WId winId() const override { return WId(m_view); } void clearAccessibleCache(); - QSurfaceFormat format() const Q_DECL_OVERRIDE; + QSurfaceFormat format() const override; - void requestUpdate() Q_DECL_OVERRIDE; + void requestUpdate() override; CAEAGLLayer *eaglLayer() const; 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.h b/src/plugins/platforms/ios/quiview.h index 1ce9007a35..3e3c579075 100644 --- a/src/plugins/platforms/ios/quiview.h +++ b/src/plugins/platforms/ios/quiview.h @@ -58,6 +58,7 @@ QT_END_NAMESPACE QT_PREPEND_NAMESPACE(QIOSWindow) *m_qioswindow; @private QHash<UITouch *, QWindowSystemInterface::TouchPoint> m_activeTouches; + UITouch *m_activePencilTouch; int m_nextTouchId; @private diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index 17506ff8fb..6960698bf1 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -49,11 +49,12 @@ #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> +Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") + @implementation QUIView + (void)load @@ -83,10 +84,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; } @@ -107,7 +109,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)) @@ -116,7 +118,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; } @@ -141,6 +142,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 @@ -188,24 +196,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]; @@ -341,7 +338,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 @@ -358,11 +355,44 @@ return [super pointInside:point withEvent:event]; } -- (void)updateTouchList:(NSSet *)touches withState:(Qt::TouchPointState)state +- (void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(Qt::TouchPointState)state withTimestamp:(ulong)timeStamp { + QIOSIntegration *iosIntegration = QIOSIntegration::instance(); bool supportsPressure = QIOSIntegration::instance()->touchDevice()->capabilities() & QTouchDevice::Pressure; - foreach (UITouch *uiTouch, m_activeTouches.keys()) { +#if QT_CONFIG(tabletevent) + if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) { + NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch]; + int i = 0; + for (UITouch *cTouch in cTouches) { + QPointF localViewPosition = QPointF::fromCGPoint([cTouch preciseLocationInView:self]); + QPoint localViewPositionI = localViewPosition.toPoint(); + QPointF globalScreenPosition = m_qioswindow->mapToGlobal(localViewPositionI) + + (localViewPosition - localViewPositionI); + qreal pressure = cTouch.force / cTouch.maximumPossibleForce; + // azimuth unit vector: +x to the right, +y going downwards + CGVector azimuth = [cTouch azimuthUnitVectorInView: self]; + // azimuthAngle given in radians, zero when the stylus points towards +x axis; converted to degrees with 0 pointing straight up + qreal azimuthAngle = [cTouch azimuthAngleInView: self] * 180 / M_PI + 90; + // altitudeAngle given in radians, pi / 2 is with the stylus perpendicular to the iPad, smaller values mean more tilted, but never negative. + // Convert to degrees with zero being perpendicular. + qreal altitudeAngle = 90 - cTouch.altitudeAngle * 180 / M_PI; + qCDebug(lcQpaTablet) << i << ":" << timeStamp << localViewPosition << pressure << state << "azimuth" << azimuth.dx << azimuth.dy + << "angle" << azimuthAngle << "altitude" << cTouch.altitudeAngle + << "xTilt" << qBound(-60.0, altitudeAngle * azimuth.dx, 60.0) << "yTilt" << qBound(-60.0, altitudeAngle * azimuth.dy, 60.0); + QWindowSystemInterface::handleTabletEvent(m_qioswindow->window(), timeStamp, localViewPosition, globalScreenPosition, + // device, pointerType, buttons + QTabletEvent::RotationStylus, QTabletEvent::Pen, state == Qt::TouchPointReleased ? Qt::NoButton : Qt::LeftButton, + // pressure, xTilt, yTilt + pressure, qBound(-60.0, altitudeAngle * azimuth.dx, 60.0), qBound(-60.0, altitudeAngle * azimuth.dy, 60.0), + // tangentialPressure, rotation, z, uid, modifiers + 0, azimuthAngle, 0, 0, Qt::NoModifier); + ++i; + } + } +#endif + + for (UITouch *uiTouch : m_activeTouches.keys()) { QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch]; if (![touches containsObject:uiTouch]) { touchPoint.state = Qt::TouchPointStationary; @@ -391,16 +421,14 @@ touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce; } else { // We don't claim that our touch device supports QTouchDevice::Pressure, - // but fill in a meaningfull value in case clients use it anyways. + // but fill in a meaningful value in case clients use it anyway. touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0; } } } -} - -- (void)sendTouchEventWithTimestamp:(ulong)timeStamp -{ - QIOSIntegration *iosIntegration = QIOSIntegration::instance(); + if (m_activeTouches.isEmpty()) + return; + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); if (!static_cast<QUIWindow *>(self.window).sendingEvent) { // The event is likely delivered as part of delayed touch delivery, via // _UIGestureEnvironmentSortAndSendDelayedTouches, due to one of the two @@ -425,8 +453,21 @@ // points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint // an id for use by Qt. for (UITouch *touch in touches) { - Q_ASSERT(!m_activeTouches.contains(touch)); - m_activeTouches[touch].id = m_nextTouchId++; +#if QT_CONFIG(tabletevent) + if (touch.type == UITouchTypeStylus) { + if (Q_UNLIKELY(m_activePencilTouch)) { + qWarning("ignoring additional Pencil while first is still active"); + continue; + } + m_activePencilTouch = touch; + } else + { + Q_ASSERT(!m_activeTouches.contains(touch)); +#endif + m_activeTouches[touch].id = m_nextTouchId++; +#if QT_CONFIG(tabletevent) + } +#endif } if (m_qioswindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) { @@ -437,31 +478,36 @@ topLevel->requestActivateWindow(); } - [self updateTouchList:touches withState:Qt::TouchPointPressed]; - [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; + [self handleTouches:touches withEvent:event withState:Qt::TouchPointPressed withTimestamp:ulong(event.timestamp * 1000)]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - [self updateTouchList:touches withState:Qt::TouchPointMoved]; - [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; + [self handleTouches:touches withEvent:event withState:Qt::TouchPointMoved withTimestamp:ulong(event.timestamp * 1000)]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - [self updateTouchList:touches withState:Qt::TouchPointReleased]; - [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; + [self handleTouches:touches withEvent:event withState:Qt::TouchPointReleased withTimestamp:ulong(event.timestamp * 1000)]; // Remove ended touch points from the active set: - for (UITouch *touch in touches) - m_activeTouches.remove(touch); - if (m_activeTouches.isEmpty()) + for (UITouch *touch in touches) { +#if QT_CONFIG(tabletevent) + if (touch.type == UITouchTypeStylus) { + m_activePencilTouch = nil; + } else +#endif + { + m_activeTouches.remove(touch); + } + } + if (m_activeTouches.isEmpty() && !m_activePencilTouch) m_nextTouchId = 0; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - if (m_activeTouches.isEmpty()) + if (m_activeTouches.isEmpty() && !m_activePencilTouch) return; // When four-finger swiping, we get a touchesCancelled callback @@ -485,11 +531,12 @@ // sub-set of the active touch events are intentionally cancelled. NSInteger count = static_cast<NSInteger>([touches count]); - if (count != 0 && count != m_activeTouches.count()) + if (count != 0 && count != m_activeTouches.count() && !m_activePencilTouch) qWarning("Subset of active touches cancelled by UIKit"); m_activeTouches.clear(); m_nextTouchId = 0; + m_activePencilTouch = nil; NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime]; 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/linuxfb/main.cpp b/src/plugins/platforms/linuxfb/main.cpp index 82b916b9a9..24156b68e8 100644 --- a/src/plugins/platforms/linuxfb/main.cpp +++ b/src/plugins/platforms/linuxfb/main.cpp @@ -47,7 +47,7 @@ class QLinuxFbIntegrationPlugin : public QPlatformIntegrationPlugin Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "linuxfb.json") public: - QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; + QPlatformIntegration *create(const QString&, const QStringList&) override; }; QPlatformIntegration* QLinuxFbIntegrationPlugin::create(const QString& system, const QStringList& paramList) diff --git a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp index e15d6fee24..dcc1ef2790 100644 --- a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp +++ b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp @@ -43,7 +43,6 @@ // Multiscreen: QWindow-QScreen(-output) association. Needs some reorg (device cannot be owned by screen) // Find card via devicediscovery like in eglfs_kms. // Mode restore like QEglFSKmsInterruptHandler. -// Formats other then 32 bpp? // grabWindow #include "qlinuxfbdrmscreen.h" @@ -187,15 +186,67 @@ void QLinuxFbDevice::registerScreen(QPlatformScreen *screen, Q_UNREACHABLE(); } +static uint32_t bppForDrmFormat(uint32_t drmFormat) +{ + switch (drmFormat) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return 16; + default: + return 32; + } +} + +static int depthForDrmFormat(uint32_t drmFormat) +{ + switch (drmFormat) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return 16; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + return 24; + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + return 30; + default: + return 32; + } +} + +static QImage::Format formatForDrmFormat(uint32_t drmFormat) +{ + switch (drmFormat) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + return QImage::Format_RGB32; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + return QImage::Format_ARGB32; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return QImage::Format_RGB16; + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + return QImage::Format_RGB30; + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + return QImage::Format_A2RGB30_Premultiplied; + default: + return QImage::Format_ARGB32; + } +} + bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx) { const QSize size = output->currentRes(); const uint32_t w = size.width(); const uint32_t h = size.height(); + const uint32_t bpp = bppForDrmFormat(output->kmsOutput.drm_format); drm_mode_create_dumb creq = { h, w, - 32, + bpp, 0, 0, 0, 0 }; if (drmIoctl(fd(), DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) { @@ -207,10 +258,15 @@ bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int buffe fb.handle = creq.handle; fb.pitch = creq.pitch; fb.size = creq.size; - qCDebug(qLcFbDrm, "Got a dumb buffer for size %dx%d, handle %u, pitch %u, size %u", - w, h, fb.handle, fb.pitch, (uint) fb.size); + qCDebug(qLcFbDrm, "Got a dumb buffer for size %dx%d and bpp %u: handle %u, pitch %u, size %u", + w, h, bpp, fb.handle, fb.pitch, (uint) fb.size); + + uint32_t handles[4] = { fb.handle }; + uint32_t strides[4] = { fb.pitch }; + uint32_t offsets[4] = { 0 }; - if (drmModeAddFB(fd(), w, h, 24, 32, fb.pitch, fb.handle, &fb.fb) == -1) { + if (drmModeAddFB2(fd(), w, h, output->kmsOutput.drm_format, + handles, strides, offsets, &fb.fb, 0) == -1) { qErrnoWarning(errno, "Failed to add FB"); return false; } @@ -229,10 +285,10 @@ bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int buffe return false; } - qCDebug(qLcFbDrm, "FB is %u, mapped at %p", fb.fb, fb.p); + qCDebug(qLcFbDrm, "FB is %u (DRM format 0x%x), mapped at %p", fb.fb, output->kmsOutput.drm_format, fb.p); memset(fb.p, 0, fb.size); - fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, QImage::Format_ARGB32); + fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, formatForDrmFormat(output->kmsOutput.drm_format)); return true; } @@ -357,10 +413,10 @@ bool QLinuxFbDrmScreen::initialize() QLinuxFbDevice::Output *output(m_device->output(0)); mGeometry = QRect(QPoint(0, 0), output->currentRes()); - mDepth = 32; - mFormat = QImage::Format_ARGB32; + mDepth = depthForDrmFormat(output->kmsOutput.drm_format); + mFormat = formatForDrmFormat(output->kmsOutput.drm_format); mPhysicalSize = output->kmsOutput.physical_size; - qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize; + qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize << mDepth << mFormat; QFbScreen::initializeCompositor(); diff --git a/src/plugins/platforms/linuxfb/qlinuxfbintegration.h b/src/plugins/platforms/linuxfb/qlinuxfbintegration.h index 9934a8cd54..22578bf980 100644 --- a/src/plugins/platforms/linuxfb/qlinuxfbintegration.h +++ b/src/plugins/platforms/linuxfb/qlinuxfbintegration.h @@ -55,19 +55,19 @@ public: QLinuxFbIntegration(const QStringList ¶mList); ~QLinuxFbIntegration(); - void initialize() Q_DECL_OVERRIDE; - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; + void initialize() override; + bool hasCapability(QPlatformIntegration::Capability cap) const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QAbstractEventDispatcher *createEventDispatcher() const override; - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; - QPlatformServices *services() const Q_DECL_OVERRIDE; - QPlatformInputContext *inputContext() const Q_DECL_OVERRIDE { return m_inputContext; } + QPlatformFontDatabase *fontDatabase() const override; + QPlatformServices *services() const override; + QPlatformInputContext *inputContext() const override { return m_inputContext; } - QPlatformNativeInterface *nativeInterface() const Q_DECL_OVERRIDE; + QPlatformNativeInterface *nativeInterface() const override; QList<QPlatformScreen *> screens() const; diff --git a/src/plugins/platforms/minimal/main.cpp b/src/plugins/platforms/minimal/main.cpp index 29809c1843..f9a0c17509 100644 --- a/src/plugins/platforms/minimal/main.cpp +++ b/src/plugins/platforms/minimal/main.cpp @@ -48,7 +48,7 @@ class QMinimalIntegrationPlugin : public QPlatformIntegrationPlugin Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "minimal.json") public: - QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; + QPlatformIntegration *create(const QString&, const QStringList&) override; }; QPlatformIntegration *QMinimalIntegrationPlugin::create(const QString& system, const QStringList& paramList) diff --git a/src/plugins/platforms/minimal/minimal.pro b/src/plugins/platforms/minimal/minimal.pro index 8cfb68824e..a1a2da547b 100644 --- a/src/plugins/platforms/minimal/minimal.pro +++ b/src/plugins/platforms/minimal/minimal.pro @@ -14,6 +14,8 @@ HEADERS = qminimalintegration.h \ OTHER_FILES += minimal.json +qtConfig(freetype): QMAKE_USE_PRIVATE += freetype + PLUGIN_TYPE = platforms PLUGIN_CLASS_NAME = QMinimalIntegrationPlugin !equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = - diff --git a/src/plugins/platforms/minimal/qminimalbackingstore.h b/src/plugins/platforms/minimal/qminimalbackingstore.h index 3d7aaf2b99..2119894809 100644 --- a/src/plugins/platforms/minimal/qminimalbackingstore.h +++ b/src/plugins/platforms/minimal/qminimalbackingstore.h @@ -52,9 +52,9 @@ public: QMinimalBackingStore(QWindow *window); ~QMinimalBackingStore(); - QPaintDevice *paintDevice() Q_DECL_OVERRIDE; - void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - void resize(const QSize &size, const QRegion &staticContents) Q_DECL_OVERRIDE; + QPaintDevice *paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; private: QImage mImage; diff --git a/src/plugins/platforms/minimal/qminimalintegration.cpp b/src/plugins/platforms/minimal/qminimalintegration.cpp index ca33689cd7..0c04608fca 100644 --- a/src/plugins/platforms/minimal/qminimalintegration.cpp +++ b/src/plugins/platforms/minimal/qminimalintegration.cpp @@ -54,11 +54,17 @@ # endif #elif defined(Q_OS_DARWIN) # include <QtFontDatabaseSupport/private/qcoretextfontdatabase_p.h> -#elif QT_CONFIG(fontconfig) +#endif + +#if QT_CONFIG(fontconfig) # include <QtFontDatabaseSupport/private/qgenericunixfontdatabase_p.h> # include <qpa/qplatformfontdatabase.h> #endif +#if QT_CONFIG(freetype) +#include <QtFontDatabaseSupport/private/qfontengine_ft_p.h> +#endif + #if !defined(Q_OS_WIN) #include <QtEventDispatcherSupport/private/qgenericunixeventdispatcher_p.h> #elif defined(Q_OS_WINRT) @@ -81,6 +87,8 @@ static inline unsigned parseOptions(const QStringList ¶mList) options |= QMinimalIntegration::EnableFonts; else if (param == QLatin1String("freetype")) options |= QMinimalIntegration::FreeTypeFontDatabase; + else if (param == QLatin1String("fontconfig")) + options |= QMinimalIntegration::FontconfigDatabase; } return options; } @@ -123,15 +131,13 @@ bool QMinimalIntegration::hasCapability(QPlatformIntegration::Capability cap) co class DummyFontDatabase : public QPlatformFontDatabase { public: - virtual void populateFontDatabase() Q_DECL_OVERRIDE {} + virtual void populateFontDatabase() override {} }; QPlatformFontDatabase *QMinimalIntegration::fontDatabase() const { if (!m_fontDatabase && (m_options & EnableFonts)) { -#if QT_CONFIG(fontconfig) - m_fontDatabase = new QGenericUnixFontDatabase; -#elif defined(Q_OS_WINRT) +#if defined(Q_OS_WINRT) m_fontDatabase = new QWinRTFontDatabase; #elif defined(Q_OS_WIN) if (m_options & FreeTypeFontDatabase) { @@ -142,10 +148,24 @@ QPlatformFontDatabase *QMinimalIntegration::fontDatabase() const m_fontDatabase = new QWindowsFontDatabase; } #elif defined(Q_OS_DARWIN) - m_fontDatabase = new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>; + if (!(m_options & FontconfigDatabase)) { + if (m_options & FreeTypeFontDatabase) { +# if QT_CONFIG(freetype) + m_fontDatabase = new QCoreTextFontDatabaseEngineFactory<QFontEngineFT>; +# endif // freetype + } else { + m_fontDatabase = new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>; + } + } +#endif + + if (!m_fontDatabase) { +#if QT_CONFIG(fontconfig) + m_fontDatabase = new QGenericUnixFontDatabase; #else - m_fontDatabase = QPlatformIntegration::fontDatabase(); + m_fontDatabase = QPlatformIntegration::fontDatabase(); #endif + } } if (!m_fontDatabase) m_fontDatabase = new DummyFontDatabase; diff --git a/src/plugins/platforms/minimal/qminimalintegration.h b/src/plugins/platforms/minimal/qminimalintegration.h index eaa2f228c5..ad1bec2112 100644 --- a/src/plugins/platforms/minimal/qminimalintegration.h +++ b/src/plugins/platforms/minimal/qminimalintegration.h @@ -51,9 +51,9 @@ public: QMinimalScreen() : mDepth(32), mFormat(QImage::Format_ARGB32_Premultiplied) {} - QRect geometry() const Q_DECL_OVERRIDE { return mGeometry; } - int depth() const Q_DECL_OVERRIDE { return mDepth; } - QImage::Format format() const Q_DECL_OVERRIDE { return mFormat; } + QRect geometry() const override { return mGeometry; } + int depth() const override { return mDepth; } + QImage::Format format() const override { return mFormat; } public: QRect mGeometry; @@ -68,18 +68,19 @@ public: enum Options { // Options to be passed on command line or determined from environment DebugBackingStore = 0x1, EnableFonts = 0x2, - FreeTypeFontDatabase = 0x4 + FreeTypeFontDatabase = 0x4, + FontconfigDatabase = 0x8 }; explicit QMinimalIntegration(const QStringList ¶meters); ~QMinimalIntegration(); - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; + bool hasCapability(QPlatformIntegration::Capability cap) const override; + QPlatformFontDatabase *fontDatabase() const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; + QAbstractEventDispatcher *createEventDispatcher() const override; unsigned options() const { return m_options; } diff --git a/src/plugins/platforms/minimalegl/main.cpp b/src/plugins/platforms/minimalegl/main.cpp index a010ed76e2..5aac71e140 100644 --- a/src/plugins/platforms/minimalegl/main.cpp +++ b/src/plugins/platforms/minimalegl/main.cpp @@ -47,7 +47,7 @@ class QMinimalEglIntegrationPlugin : public QPlatformIntegrationPlugin Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "minimalegl.json") public: - QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; + QPlatformIntegration *create(const QString&, const QStringList&) override; }; QPlatformIntegration* QMinimalEglIntegrationPlugin::create(const QString& system, const QStringList& paramList) diff --git a/src/plugins/platforms/minimalegl/qminimaleglbackingstore.h b/src/plugins/platforms/minimalegl/qminimaleglbackingstore.h index 0e22298891..382f2d1404 100644 --- a/src/plugins/platforms/minimalegl/qminimaleglbackingstore.h +++ b/src/plugins/platforms/minimalegl/qminimaleglbackingstore.h @@ -53,13 +53,13 @@ public: QMinimalEglBackingStore(QWindow *window); ~QMinimalEglBackingStore(); - QPaintDevice *paintDevice() Q_DECL_OVERRIDE; + QPaintDevice *paintDevice() override; - void beginPaint(const QRegion &) Q_DECL_OVERRIDE; - void endPaint() Q_DECL_OVERRIDE; + void beginPaint(const QRegion &) override; + void endPaint() override; - void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - void resize(const QSize &size, const QRegion &staticContents) Q_DECL_OVERRIDE; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; private: QOpenGLContext *m_context; diff --git a/src/plugins/platforms/minimalegl/qminimaleglintegration.cpp b/src/plugins/platforms/minimalegl/qminimaleglintegration.cpp index a716a6092a..5d31af53d5 100644 --- a/src/plugins/platforms/minimalegl/qminimaleglintegration.cpp +++ b/src/plugins/platforms/minimalegl/qminimaleglintegration.cpp @@ -71,7 +71,7 @@ public: QWinRTEventDispatcher() {} protected: - bool hasPendingEvents() Q_DECL_OVERRIDE + bool hasPendingEvents() override { return QEventDispatcherWinRT::hasPendingEvents() || QWindowSystemInterface::windowSystemEventsQueued(); } @@ -156,7 +156,7 @@ QAbstractEventDispatcher *QMinimalEglIntegration::createEventDispatcher() const #elif defined(Q_OS_WIN) return new QWindowsGuiEventDispatcher; #else - return Q_NULLPTR; + return nullptr; #endif } diff --git a/src/plugins/platforms/minimalegl/qminimaleglintegration.h b/src/plugins/platforms/minimalegl/qminimaleglintegration.h index d0ab75bd3c..70a51004a6 100644 --- a/src/plugins/platforms/minimalegl/qminimaleglintegration.h +++ b/src/plugins/platforms/minimalegl/qminimaleglintegration.h @@ -51,18 +51,18 @@ public: QMinimalEglIntegration(); ~QMinimalEglIntegration(); - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; + bool hasCapability(QPlatformIntegration::Capability cap) const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; #ifndef QT_NO_OPENGL - QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; #endif - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; + QPlatformFontDatabase *fontDatabase() const override; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QAbstractEventDispatcher *createEventDispatcher() const override; - QVariant styleHint(QPlatformIntegration::StyleHint hint) const Q_DECL_OVERRIDE; + QVariant styleHint(QPlatformIntegration::StyleHint hint) const override; private: QPlatformFontDatabase *mFontDb; diff --git a/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp b/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp index 0175d2dbdd..6e122e28ce 100644 --- a/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp +++ b/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp @@ -64,7 +64,7 @@ public: { } - EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface) Q_DECL_OVERRIDE + EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface) override { QMinimalEglWindow *window = static_cast<QMinimalEglWindow *>(surface); QMinimalEglScreen *screen = static_cast<QMinimalEglScreen *>(window->screen()); diff --git a/src/plugins/platforms/minimalegl/qminimaleglscreen.h b/src/plugins/platforms/minimalegl/qminimaleglscreen.h index 24098b8127..926936ae32 100644 --- a/src/plugins/platforms/minimalegl/qminimaleglscreen.h +++ b/src/plugins/platforms/minimalegl/qminimaleglscreen.h @@ -56,9 +56,9 @@ public: QMinimalEglScreen(EGLNativeDisplayType display); ~QMinimalEglScreen(); - QRect geometry() const Q_DECL_OVERRIDE; - int depth() const Q_DECL_OVERRIDE; - QImage::Format format() const Q_DECL_OVERRIDE; + QRect geometry() const override; + int depth() const override; + QImage::Format format() const override; #ifndef QT_NO_OPENGL QPlatformOpenGLContext *platformContext() const; #endif diff --git a/src/plugins/platforms/minimalegl/qminimaleglwindow.h b/src/plugins/platforms/minimalegl/qminimaleglwindow.h index b8bfd6c8d2..098ec05e6b 100644 --- a/src/plugins/platforms/minimalegl/qminimaleglwindow.h +++ b/src/plugins/platforms/minimalegl/qminimaleglwindow.h @@ -51,8 +51,8 @@ class QMinimalEglWindow : public QPlatformWindow public: QMinimalEglWindow(QWindow *w); - void setGeometry(const QRect &) Q_DECL_OVERRIDE; - WId winId() const Q_DECL_OVERRIDE; + void setGeometry(const QRect &) override; + WId winId() const override; private: WId m_winid; diff --git a/src/plugins/platforms/mirclient/qmirclientinput.cpp b/src/plugins/platforms/mirclient/qmirclientinput.cpp index ea13f3cc17..e5319b0435 100644 --- a/src/plugins/platforms/mirclient/qmirclientinput.cpp +++ b/src/plugins/platforms/mirclient/qmirclientinput.cpp @@ -156,6 +156,36 @@ static const uint32_t KeyTable[] = { XKB_KEY_dead_belowdot, Qt::Key_Dead_Belowdot, XKB_KEY_dead_hook, Qt::Key_Dead_Hook, XKB_KEY_dead_horn, Qt::Key_Dead_Horn, + XKB_KEY_dead_stroke, Qt::Key_Dead_Stroke, + XKB_KEY_dead_abovecomma, Qt::Key_Dead_Abovecomma, + XKB_KEY_dead_abovereversedcomma, Qt::Key_Dead_Abovereversedcomma, + XKB_KEY_dead_doublegrave, Qt::Key_Dead_Doublegrave, + XKB_KEY_dead_belowring, Qt::Key_Dead_Belowring, + XKB_KEY_dead_belowmacron, Qt::Key_Dead_Belowmacron, + XKB_KEY_dead_belowcircumflex, Qt::Key_Dead_Belowcircumflex, + XKB_KEY_dead_belowtilde, Qt::Key_Dead_Belowtilde, + XKB_KEY_dead_belowbreve, Qt::Key_Dead_Belowbreve, + XKB_KEY_dead_belowdiaeresis, Qt::Key_Dead_Belowdiaeresis, + XKB_KEY_dead_invertedbreve, Qt::Key_Dead_Invertedbreve, + XKB_KEY_dead_belowcomma, Qt::Key_Dead_Belowcomma, + XKB_KEY_dead_currency, Qt::Key_Dead_Currency, + XKB_KEY_dead_a, Qt::Key_Dead_a, + XKB_KEY_dead_A, Qt::Key_Dead_A, + XKB_KEY_dead_e, Qt::Key_Dead_e, + XKB_KEY_dead_E, Qt::Key_Dead_E, + XKB_KEY_dead_i, Qt::Key_Dead_i, + XKB_KEY_dead_I, Qt::Key_Dead_I, + XKB_KEY_dead_o, Qt::Key_Dead_o, + XKB_KEY_dead_O, Qt::Key_Dead_O, + XKB_KEY_dead_u, Qt::Key_Dead_u, + XKB_KEY_dead_U, Qt::Key_Dead_U, + XKB_KEY_dead_small_schwa, Qt::Key_Dead_Small_Schwa, + XKB_KEY_dead_capital_schwa, Qt::Key_Dead_Capital_Schwa, + XKB_KEY_dead_greek, Qt::Key_Dead_Greek, + XKB_KEY_dead_lowline, Qt::Key_Dead_Lowline, + XKB_KEY_dead_aboveverticalline, Qt::Key_Dead_Aboveverticalline, + XKB_KEY_dead_belowverticalline, Qt::Key_Dead_Belowverticalline, + XKB_KEY_dead_longsolidusoverlay, Qt::Key_Dead_Longsolidusoverlay, XKB_KEY_Mode_switch, Qt::Key_Mode_switch, XKB_KEY_script_switch, Qt::Key_Mode_switch, 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/main.cpp b/src/plugins/platforms/offscreen/main.cpp index 9750c4f7ca..207db60f3a 100644 --- a/src/plugins/platforms/offscreen/main.cpp +++ b/src/plugins/platforms/offscreen/main.cpp @@ -48,7 +48,7 @@ class QOffscreenIntegrationPlugin : public QPlatformIntegrationPlugin Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "offscreen.json") public: - QPlatformIntegration *create(const QString&, const QStringList&) Q_DECL_OVERRIDE; + QPlatformIntegration *create(const QString&, const QStringList&) override; }; QPlatformIntegration *QOffscreenIntegrationPlugin::create(const QString& system, const QStringList& paramList) diff --git a/src/plugins/platforms/offscreen/qoffscreencommon.cpp b/src/plugins/platforms/offscreen/qoffscreencommon.cpp index 85422071aa..f0eb69718a 100644 --- a/src/plugins/platforms/offscreen/qoffscreencommon.cpp +++ b/src/plugins/platforms/offscreen/qoffscreencommon.cpp @@ -55,8 +55,8 @@ class QOffscreenCursor : public QPlatformCursor public: QOffscreenCursor() : m_pos(10, 10) {} - QPoint pos() const Q_DECL_OVERRIDE { return m_pos; } - void setPos(const QPoint &pos) Q_DECL_OVERRIDE + QPoint pos() const override { return m_pos; } + void setPos(const QPoint &pos) override { m_pos = pos; const QWindowList wl = QGuiApplication::topLevelWindows(); @@ -82,7 +82,7 @@ public: QOffscreenScreen::windowContainingCursor = containing ? containing->handle() : 0; } #ifndef QT_NO_CURSOR - void changeCursor(QCursor *windowCursor, QWindow *window) Q_DECL_OVERRIDE + void changeCursor(QCursor *windowCursor, QWindow *window) override { Q_UNUSED(windowCursor); Q_UNUSED(window); diff --git a/src/plugins/platforms/offscreen/qoffscreencommon.h b/src/plugins/platforms/offscreen/qoffscreencommon.h index 72d6f16d26..541c07384c 100644 --- a/src/plugins/platforms/offscreen/qoffscreencommon.h +++ b/src/plugins/platforms/offscreen/qoffscreencommon.h @@ -57,12 +57,12 @@ class QOffscreenScreen : public QPlatformScreen public: QOffscreenScreen(); - QRect geometry() const Q_DECL_OVERRIDE { return m_geometry; } - int depth() const Q_DECL_OVERRIDE { return 32; } - QImage::Format format() const Q_DECL_OVERRIDE { return QImage::Format_RGB32; } - QPlatformCursor *cursor() const Q_DECL_OVERRIDE { return m_cursor.data(); } + QRect geometry() const override { return m_geometry; } + int depth() const override { return 32; } + QImage::Format format() const override { return QImage::Format_RGB32; } + QPlatformCursor *cursor() const override { return m_cursor.data(); } - QPixmap grabWindow(WId window, int x, int y, int width, int height) const Q_DECL_OVERRIDE; + QPixmap grabWindow(WId window, int x, int y, int width, int height) const override; static QPlatformWindow *windowContainingCursor; @@ -75,8 +75,7 @@ public: class QOffscreenDrag : public QPlatformDrag { public: - QMimeData *platformDropData() Q_DECL_OVERRIDE { return 0; } - Qt::DropAction drag(QDrag *) Q_DECL_OVERRIDE { return Qt::IgnoreAction; } + Qt::DropAction drag(QDrag *) override { return Qt::IgnoreAction; } }; #endif @@ -86,10 +85,10 @@ public: QOffscreenBackingStore(QWindow *window); ~QOffscreenBackingStore(); - QPaintDevice *paintDevice() Q_DECL_OVERRIDE; - void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) Q_DECL_OVERRIDE; - void resize(const QSize &size, const QRegion &staticContents) Q_DECL_OVERRIDE; - bool scroll(const QRegion &area, int dx, int dy) Q_DECL_OVERRIDE; + QPaintDevice *paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; + bool scroll(const QRegion &area, int dx, int dy) override; QPixmap grabWindow(WId window, const QRect &rect) const; 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..a1e3a9bde8 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration.h +++ b/src/plugins/platforms/offscreen/qoffscreenintegration.h @@ -54,17 +54,23 @@ public: QOffscreenIntegration(); ~QOffscreenIntegration(); - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; + void initialize() override; + bool hasCapability(QPlatformIntegration::Capability cap) const override; - QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; - QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const Q_DECL_OVERRIDE; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; #ifndef QT_NO_DRAGANDDROP - QPlatformDrag *drag() const Q_DECL_OVERRIDE; + QPlatformDrag *drag() const override; #endif - QPlatformServices *services() const Q_DECL_OVERRIDE; - QPlatformFontDatabase *fontDatabase() const Q_DECL_OVERRIDE; - QAbstractEventDispatcher *createEventDispatcher() const Q_DECL_OVERRIDE; + QPlatformInputContext *inputContext() const override; + QPlatformServices *services() const override; + + QPlatformFontDatabase *fontDatabase() const override; + QAbstractEventDispatcher *createEventDispatcher() const override; + + QStringList themeNames() const override; + QPlatformTheme *createPlatformTheme(const QString &name) const override; static QOffscreenIntegration *createOffscreenIntegration(); @@ -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/qoffscreenintegration_x11.h b/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h index aaca74d2fb..5e1c6b799b 100644 --- a/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h +++ b/src/plugins/platforms/offscreen/qoffscreenintegration_x11.h @@ -55,9 +55,9 @@ class QOffscreenX11Info; class QOffscreenX11Integration : public QOffscreenIntegration { public: - bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; + bool hasCapability(QPlatformIntegration::Capability cap) const override; - QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; private: mutable QScopedPointer<QOffscreenX11Connection> m_connection; @@ -88,14 +88,14 @@ public: QOffscreenX11GLXContext(QOffscreenX11Info *x11, QOpenGLContext *context); ~QOffscreenX11GLXContext(); - bool makeCurrent(QPlatformSurface *surface) Q_DECL_OVERRIDE; - void doneCurrent() Q_DECL_OVERRIDE; - void swapBuffers(QPlatformSurface *surface) Q_DECL_OVERRIDE; - QFunctionPointer getProcAddress(const char *procName) Q_DECL_OVERRIDE; + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; + void swapBuffers(QPlatformSurface *surface) override; + QFunctionPointer getProcAddress(const char *procName) override; - QSurfaceFormat format() const Q_DECL_OVERRIDE; - bool isSharing() const Q_DECL_OVERRIDE; - bool isValid() const Q_DECL_OVERRIDE; + QSurfaceFormat format() const override; + bool isSharing() const override; + bool isValid() const override; private: QScopedPointer<QOffscreenX11GLXContextData> d; 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..e1f37bb034 100644 --- a/src/plugins/platforms/offscreen/qoffscreenwindow.h +++ b/src/plugins/platforms/offscreen/qoffscreenwindow.h @@ -53,15 +53,15 @@ public: QOffscreenWindow(QWindow *window); ~QOffscreenWindow(); - void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; + void setGeometry(const QRect &rect) override; + void setWindowState(Qt::WindowStates states) override; - QMargins frameMargins() const Q_DECL_OVERRIDE; + QMargins frameMargins() const override; - void setVisible(bool visible) Q_DECL_OVERRIDE; - void requestActivateWindow() Q_DECL_OVERRIDE; + void setVisible(bool visible) override; + void requestActivateWindow() override; - WId winId() const Q_DECL_OVERRIDE; + WId winId() const override; static QOffscreenWindow *windowForWinId(WId id); diff --git a/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp b/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp index a758bdf7f4..dd7f907ee0 100644 --- a/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp +++ b/src/plugins/platforms/qnx/qqnxrasterbackingstore.cpp @@ -139,6 +139,7 @@ void QQnxRasterBackingStore::beginPaint(const QRegion ®ion) platformWindow()->adjustBufferSize(); if (window()->requestedFormat().alphaBufferSize() > 0) { + auto platformScreen = static_cast<QQnxScreen *>(platformWindow()->screen()); for (const QRect &r : region) { // Clear transparent regions const int bg[] = { @@ -149,11 +150,11 @@ void QQnxRasterBackingStore::beginPaint(const QRegion ®ion) SCREEN_BLIT_DESTINATION_HEIGHT, r.height(), SCREEN_BLIT_END }; - Q_SCREEN_CHECKERROR(screen_fill(platformWindow()->screen()->nativeContext(), + Q_SCREEN_CHECKERROR(screen_fill(platformScreen->nativeContext(), platformWindow()->renderBuffer().nativeBuffer(), bg), "failed to clear transparent regions"); } - Q_SCREEN_CHECKERROR(screen_flush_blits(platformWindow()->screen()->nativeContext(), + Q_SCREEN_CHECKERROR(screen_flush_blits(platformScreen->nativeContext(), SCREEN_WAIT_IDLE), "failed to flush blits"); } } diff --git a/src/plugins/platforms/qnx/qqnxrasterwindow.cpp b/src/plugins/platforms/qnx/qqnxrasterwindow.cpp index 7f11de228e..dc844189d1 100644 --- a/src/plugins/platforms/qnx/qqnxrasterwindow.cpp +++ b/src/plugins/platforms/qnx/qqnxrasterwindow.cpp @@ -141,6 +141,7 @@ QQnxBuffer &QQnxRasterWindow::renderBuffer() // Check if render buffer is invalid if (m_currentBufferIndex == -1) { + auto platformScreen = static_cast<QQnxScreen *>(screen()); // Get all buffers available for rendering screen_buffer_t buffers[MAX_BUFFER_COUNT]; const int result = screen_get_window_property_pv(nativeHandle(), SCREEN_PROPERTY_RENDER_BUFFERS, @@ -153,11 +154,11 @@ QQnxBuffer &QQnxRasterWindow::renderBuffer() // Clear Buffer int bg[] = { SCREEN_BLIT_COLOR, 0x00000000, SCREEN_BLIT_END }; - Q_SCREEN_CHECKERROR(screen_fill(screen()->nativeContext(), buffers[i], bg), + Q_SCREEN_CHECKERROR(screen_fill(platformScreen->nativeContext(), buffers[i], bg), "Failed to clear window buffer"); } - Q_SCREEN_CHECKERROR(screen_flush_blits(screen()->nativeContext(), 0), + Q_SCREEN_CHECKERROR(screen_flush_blits(platformScreen->nativeContext(), 0), "Failed to flush blits"); // Use the first available render buffer @@ -185,7 +186,7 @@ void QQnxRasterWindow::adjustBufferSize() int QQnxRasterWindow::pixelFormat() const { - return screen()->nativeFormat(); + return static_cast<QQnxScreen *>(screen())->nativeFormat(); } void QQnxRasterWindow::resetBuffers() diff --git a/src/plugins/platforms/qnx/qqnxwindow.cpp b/src/plugins/platforms/qnx/qqnxwindow.cpp index 30288ccb20..38b61fd782 100644 --- a/src/plugins/platforms/qnx/qqnxwindow.cpp +++ b/src/plugins/platforms/qnx/qqnxwindow.cpp @@ -479,7 +479,7 @@ void QQnxWindow::setParent(const QPlatformWindow *window) if (newParent == m_parentWindow) return; - if (screen()->rootWindow() == this) { + if (static_cast<QQnxScreen *>(screen())->rootWindow() == this) { qWarning("Application window cannot be reparented"); return; } @@ -539,7 +539,7 @@ void QQnxWindow::requestActivateWindow() if (focusWindow == this) return; - if (screen()->rootWindow() == this || + if (static_cast<QQnxScreen *>(screen())->rootWindow() == this || (focusWindow && findWindow(focusWindow->nativeHandle()))) { // If the focus window is a child, we can just set the focus of our own window // group to our window handle @@ -550,6 +550,7 @@ void QQnxWindow::requestActivateWindow() QQnxWindow *currentWindow = this; QList<QQnxWindow*> windowList; while (currentWindow) { + auto platformScreen = static_cast<QQnxScreen *>(screen()); windowList.prepend(currentWindow); // If we find the focus window, we don't have to go further if (currentWindow == focusWindow) @@ -557,9 +558,9 @@ void QQnxWindow::requestActivateWindow() if (currentWindow->parent()){ currentWindow = static_cast<QQnxWindow*>(currentWindow->parent()); - } else if (screen()->rootWindow() && - screen()->rootWindow()->m_windowGroupName == currentWindow->m_parentGroupName) { - currentWindow = screen()->rootWindow(); + } else if (platformScreen->rootWindow() && + platformScreen->rootWindow()->m_windowGroupName == currentWindow->m_parentGroupName) { + currentWindow = platformScreen->rootWindow(); } else { currentWindow = 0; } @@ -586,7 +587,7 @@ void QQnxWindow::setFocus(screen_window_t newFocusWindow) } } -void QQnxWindow::setWindowState(Qt::WindowState state) +void QQnxWindow::setWindowState(Qt::WindowStates state) { qWindowDebug() << "state =" << state; @@ -622,6 +623,11 @@ void QQnxWindow::clearMMRendererWindow() m_mmRendererWindow = 0; } +QPlatformScreen *QQnxWindow::screen() const +{ + return m_screen; +} + QQnxWindow *QQnxWindow::findWindow(screen_window_t windowHandle) { if (m_window == windowHandle) @@ -749,32 +755,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); } } @@ -788,7 +781,8 @@ void QQnxWindow::windowPosted() bool QQnxWindow::shouldMakeFullScreen() const { - return ((screen()->rootWindow() == this) && (QQnxIntegration::options() & QQnxIntegration::FullScreenApplication)); + return ((static_cast<QQnxScreen *>(screen())->rootWindow() == this) + && (QQnxIntegration::options() & QQnxIntegration::FullScreenApplication)); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/qnx/qqnxwindow.h b/src/plugins/platforms/qnx/qqnxwindow.h index 223ea6e349..2895a547b1 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; @@ -94,7 +94,7 @@ public: void setMMRendererWindow(screen_window_t handle); void clearMMRendererWindow(); - QQnxScreen *screen() const { return m_screen; } + QPlatformScreen *screen() const override; const QList<QQnxWindow*>& children() const { return m_childWindows; } QQnxWindow *findWindow(screen_window_t windowHandle); @@ -142,7 +142,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/qvnc.cpp b/src/plugins/platforms/vnc/qvnc.cpp index fa65e8c9a4..44fc1c6101 100644 --- a/src/plugins/platforms/vnc/qvnc.cpp +++ b/src/plugins/platforms/vnc/qvnc.cpp @@ -476,7 +476,8 @@ void QRfbRawEncoder::write() // rgn &= QRect(0, 0, server->screen()->geometry().width(), // server->screen()->geometry().height()); // } - const QVector<QRect> rects = rgn.rects(); + + const auto rectsInRegion = rgn.rectCount(); { const char tmp[2] = { 0, 0 }; // msg type, padding @@ -484,16 +485,16 @@ void QRfbRawEncoder::write() } { - const quint16 count = htons(rects.size()); + const quint16 count = htons(rectsInRegion); socket->write((char *)&count, sizeof(count)); } - if (rects.size() <= 0) + if (rectsInRegion <= 0) return; const QImage screenImage = client->server()->screenImage(); - for (const QRect &tileRect: rects) { + for (const QRect &tileRect: rgn) { const QRfbRect rect(tileRect.x(), tileRect.y(), tileRect.width(), tileRect.height()); rect.write(socket); 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 deleted file mode 100644 index 0e3aacc558..0000000000 --- a/src/plugins/platforms/windows/accessible/accessible.pri +++ /dev/null @@ -1,18 +0,0 @@ -SOURCES += \ - $$PWD/qwindowsaccessibility.cpp \ - $$PWD/comutils.cpp - -HEADERS += \ - $$PWD/qwindowsaccessibility.h \ - $$PWD/comutils.h - -SOURCES += $$PWD/qwindowsmsaaaccessible.cpp -HEADERS += $$PWD/qwindowsmsaaaccessible.h - -!mingw: { - SOURCES += $$PWD/iaccessible2.cpp - HEADERS += $$PWD/iaccessible2.h - include(../../../../3rdparty/iaccessible2/iaccessible2.pri) -} - -mingw: LIBS *= -luuid diff --git a/src/plugins/platforms/windows/accessible/comutils.cpp b/src/plugins/platforms/windows/accessible/comutils.cpp deleted file mode 100644 index 1c072c5e2c..0000000000 --- a/src/plugins/platforms/windows/accessible/comutils.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <qt_windows.h> - -#include <ocidl.h> -#include <olectl.h> - -#include "comutils.h" -#include <QtCore/qdatetime.h> -#include <QtGui/qpixmap.h> -#include <QtGui/qfont.h> - - -#include <QtCore/qvariant.h> -#include <QtCore/qbytearray.h> -#include <QtGui/qcolor.h> - -QT_BEGIN_NAMESPACE - -static DATE QDateTimeToDATE(const QDateTime &dt) -{ - if (!dt.isValid() || dt.isNull()) - return 949998; // Special value for no date (01/01/4501) - - SYSTEMTIME stime; - memset(&stime, 0, sizeof(stime)); - QDate date = dt.date(); - QTime time = dt.time(); - if (date.isValid() && !date.isNull()) { - stime.wDay = WORD(date.day()); - stime.wMonth = WORD(date.month()); - stime.wYear = WORD(date.year()); - } - if (time.isValid() && !time.isNull()) { - stime.wMilliseconds = WORD(time.msec()); - stime.wSecond = WORD(time.second()); - stime.wMinute = WORD(time.minute()); - stime.wHour = WORD(time.hour()); - } - - double vtime; - SystemTimeToVariantTime(&stime, &vtime); - - return vtime; -} - -inline uint QColorToOLEColor(const QColor &col) -{ - return qRgba(col.blue(), col.green(), col.red(), 0x00); -} - -bool QVariant2VARIANT(const QVariant &var, VARIANT &arg, const QByteArray &typeName, bool out) -{ - QVariant qvar = var; - // "type" is the expected type, so coerce if necessary - QVariant::Type proptype = typeName.isEmpty() ? QVariant::Invalid : QVariant::nameToType(typeName); - if (proptype == QVariant::UserType && !typeName.isEmpty()) { - if (typeName == "short" || typeName == "char") - proptype = QVariant::Int; - else if (typeName == "float") - proptype = QVariant::Double; - } - if (proptype != QVariant::Invalid && proptype != QVariant::UserType && proptype != qvar.type()) { - if (qvar.canConvert(int(proptype))) - qvar.convert(int(proptype)); - else - qvar = QVariant(proptype); - } - - if (out && arg.vt == (VT_VARIANT|VT_BYREF) && arg.pvarVal) { - return QVariant2VARIANT(var, *arg.pvarVal, typeName, false); - } - - if (out && proptype == QVariant::UserType && typeName == "QVariant") { - VARIANT *pVariant = new VARIANT; - QVariant2VARIANT(var, *pVariant, QByteArray(), false); - arg.vt = VT_VARIANT|VT_BYREF; - arg.pvarVal = pVariant; - return true; - } - - switch ((int)qvar.type()) { - case QVariant::String: - if (out && arg.vt == (VT_BSTR|VT_BYREF)) { - if (*arg.pbstrVal) - SysFreeString(*arg.pbstrVal); - *arg.pbstrVal = QStringToBSTR(qvar.toString()); - arg.vt = VT_BSTR|VT_BYREF; - } else { - arg.vt = VT_BSTR; - arg.bstrVal = QStringToBSTR(qvar.toString()); - if (out) { - arg.pbstrVal = new BSTR(arg.bstrVal); - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::Int: - if (out && arg.vt == (VT_I4|VT_BYREF)) { - *arg.plVal = qvar.toInt(); - } else { - arg.vt = VT_I4; - arg.lVal = qvar.toInt(); - if (out) { - if (typeName == "short") { - arg.vt = VT_I2; - arg.piVal = new short(arg.lVal); - } else if (typeName == "char") { - arg.vt = VT_I1; - arg.pcVal= new char(arg.lVal); - } else { - arg.plVal = new long(arg.lVal); - } - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::UInt: - if (out && (arg.vt == (VT_UINT|VT_BYREF) || arg.vt == (VT_I4|VT_BYREF))) { - *arg.puintVal = qvar.toUInt(); - } else { - arg.vt = VT_UINT; - arg.uintVal = qvar.toUInt(); - if (out) { - arg.puintVal = new uint(arg.uintVal); - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::LongLong: - if (out && arg.vt == (VT_CY|VT_BYREF)) { - arg.pcyVal->int64 = qvar.toLongLong(); - } else if (out && arg.vt == (VT_I8|VT_BYREF)) { - *arg.pllVal = qvar.toLongLong(); - } else { - arg.vt = VT_I8; - arg.llVal = qvar.toLongLong(); - if (out) { - arg.pllVal = new LONGLONG(arg.llVal); - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::ULongLong: - if (out && arg.vt == (VT_CY|VT_BYREF)) { - arg.pcyVal->int64 = qvar.toULongLong(); - } else if (out && arg.vt == (VT_UI8|VT_BYREF)) { - *arg.pullVal = qvar.toULongLong(); - } else { - arg.vt = VT_UI8; - arg.ullVal = qvar.toULongLong(); - if (out) { - arg.pullVal = new ULONGLONG(arg.ullVal); - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::Bool: - if (out && arg.vt == (VT_BOOL|VT_BYREF)) { - *arg.pboolVal = qvar.toBool() ? VARIANT_TRUE : VARIANT_FALSE; - } else { - arg.vt = VT_BOOL; - arg.boolVal = qvar.toBool() ? VARIANT_TRUE : VARIANT_FALSE; - if (out) { - arg.pboolVal = new short(arg.boolVal); - arg.vt |= VT_BYREF; - } - } - break; - case QVariant::Double: - if (out && arg.vt == (VT_R8|VT_BYREF)) { - *arg.pdblVal = qvar.toDouble(); - } else { - arg.vt = VT_R8; - arg.dblVal = qvar.toDouble(); - if (out) { - if (typeName == "float") { - arg.vt = VT_R4; - arg.pfltVal = new float(arg.dblVal); - } else { - arg.pdblVal = new double(arg.dblVal); - } - arg.vt |= VT_BYREF; - } - } - break; - case QVariant::Color: - if (out && arg.vt == (VT_COLOR|VT_BYREF)) { - - *arg.plVal = QColorToOLEColor(qvariant_cast<QColor>(qvar)); - } else { - arg.vt = VT_COLOR; - arg.lVal = QColorToOLEColor(qvariant_cast<QColor>(qvar)); - if (out) { - arg.plVal = new long(arg.lVal); - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::Date: - case QVariant::Time: - case QVariant::DateTime: - if (out && arg.vt == (VT_DATE|VT_BYREF)) { - *arg.pdate = QDateTimeToDATE(qvar.toDateTime()); - } else { - arg.vt = VT_DATE; - arg.date = QDateTimeToDATE(qvar.toDateTime()); - if (out) { - arg.pdate = new DATE(arg.date); - arg.vt |= VT_BYREF; - } - } - break; - - case QVariant::Invalid: // default-parameters not set - if (out && arg.vt == (VT_ERROR|VT_BYREF)) { - *arg.plVal = DISP_E_PARAMNOTFOUND; - } else { - arg.vt = VT_ERROR; - arg.lVal = DISP_E_PARAMNOTFOUND; - if (out) { - arg.plVal = new long(arg.lVal); - arg.vt |= VT_BYREF; - } - } - break; - - default: - return false; - } - - Q_ASSERT(!out || (arg.vt & VT_BYREF)); - return true; -} - -QT_END_NAMESPACE - diff --git a/src/plugins/platforms/windows/accessible/iaccessible2.cpp b/src/plugins/platforms/windows/accessible/iaccessible2.cpp deleted file mode 100644 index f9e8231843..0000000000 --- a/src/plugins/platforms/windows/accessible/iaccessible2.cpp +++ /dev/null @@ -1,1744 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QtCore/QtConfig> -#ifndef QT_NO_ACCESSIBILITY - -#include "iaccessible2.h" -#include "qwindowsaccessibility.h" -#include <QtAccessibilitySupport/private/qaccessiblebridgeutils_p.h> -#include <QtGui/qaccessible.h> -#include <QtGui/qclipboard.h> -#include <QtGui/qguiapplication.h> -#include <QtGui/private/qhighdpiscaling_p.h> -#include <QtCore/qdebug.h> - -#include <algorithm> - -QT_BEGIN_NAMESPACE - -template <class T> -static inline T *coTaskMemAllocArray(int size) -{ - return static_cast<T *>(::CoTaskMemAlloc(sizeof(T) * size_t(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(); - *name = QStringToBSTR(appName); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE AccessibleApplication::get_appVersion(/* [retval][out] */ BSTR *version) -{ - const QString appName = QGuiApplication::applicationVersion(); - *version = QStringToBSTR(appName); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE AccessibleApplication::get_toolkitName(/* [retval][out] */ BSTR *name) -{ - *name = ::SysAllocString(L"Qt"); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE AccessibleApplication::get_toolkitVersion(/* [retval][out] */ BSTR *version) -{ - *version = ::SysAllocString(TEXT(QT_VERSION_STR)); - return S_OK; -} - - -/**************************************************************\ - * AccessibleRelation * - **************************************************************/ -AccessibleRelation::AccessibleRelation(const QList<QAccessibleInterface *> &targets, - QAccessible::Relation relation) - : m_targets(targets), m_relation(relation), m_ref(1) -{ - 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) -{ - *relationType = relationToBSTR(m_relation); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE AccessibleRelation::get_localizedRelationType( - /* [retval][out] */ BSTR *localizedRelationType) -{ - // Who ever needs this??? - *localizedRelationType = relationToBSTR(m_relation); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE AccessibleRelation::get_nTargets( - /* [retval][out] */ long *nTargets) -{ - // ### always one target - *nTargets = m_targets.count(); - return S_OK; -} - -/*! - \internal - Client allocates and deallocates array - (see "Special Consideration when using Arrays", in Accessible2.idl) - */ -HRESULT STDMETHODCALLTYPE AccessibleRelation::get_target( - /* [in] */ long targetIndex, - /* [retval][out] */ IUnknown **target) -{ - if (targetIndex >= 0 && targetIndex < m_targets.count()) { - QAccessibleInterface *iface = m_targets.at(targetIndex); - *target = QWindowsAccessibility::wrap(iface); - if (*target) - return S_OK; - return E_FAIL; - } - return E_INVALIDARG; -} - -/*! - \internal - Client allocates and deallocates \a targets array - (see "Special Consideration when using Arrays", in Accessible2.idl) - */ -HRESULT STDMETHODCALLTYPE AccessibleRelation::get_targets( - /* [in] */ long maxTargets, - /* [length_is][size_is][out] */ IUnknown **targets, - /* [retval][out] */ long *nTargets) -{ - - const int numTargets = qMin(int(maxTargets), m_targets.count()); - for (int i = 0; i < numTargets; ++i) { - QAccessibleInterface *iface = m_targets.at(i); - IAccessible *iacc = QWindowsAccessibility::wrap(iface); - if (!iacc) - return E_FAIL; - *targets = iacc; - ++targets; - } - *nTargets = numTargets; - // \a targets array is allocated by client. - return numTargets > 0 ? S_OK : S_FALSE; -} - - -/**************************************************************\ - * * - * IUnknown * - * * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::QueryInterface(REFIID id, LPVOID *iface) -{ - 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; - } - } - return hr; -} - - -/* Note that IUnknown is inherited from several interfaces. Therefore we must reimplement all its - functions in the concrete class to avoid ambiguity. -*/ -ULONG STDMETHODCALLTYPE QWindowsIA2Accessible::AddRef() -{ - return QWindowsMsaaAccessible::AddRef(); -} - -ULONG STDMETHODCALLTYPE QWindowsIA2Accessible::Release() -{ - return QWindowsMsaaAccessible::Release(); -} - -/**************************************************************\ - * * - * IAccessible2 * - * * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nRelations(long *nRelations) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!nRelations) - return E_INVALIDARG; - if (!accessible) - return E_FAIL; - - return getRelationsHelper(0, 0, 0, nRelations); -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_relation(long relationIndex, IAccessibleRelation **relation) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!relation) - return E_INVALIDARG; - if (!accessible) - return E_FAIL; - - return getRelationsHelper(relation, relationIndex, 1); -} - -/*! - \internal - Client allocates and deallocates array - (see "Special Consideration when using Arrays", in Accessible2.idl) - */ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_relations(long maxRelations, - IAccessibleRelation **relations, - long *nRelations) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - return getRelationsHelper(relations, 0, maxRelations, nRelations); -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::role(long *ia2role) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - long r = accessible->role(); - - switch (r) { - case QAccessible::LayeredPane: r = IA2_ROLE_LAYERED_PANE; break; - case QAccessible::Terminal: r = IA2_ROLE_TERMINAL; break; - case QAccessible::Desktop: r = IA2_ROLE_DESKTOP_PANE; break; - case QAccessible::Paragraph: r = IA2_ROLE_PARAGRAPH; break; - case QAccessible::Section: r = IA2_ROLE_SECTION; break; - default: break; - } - - *ia2role = r; - return S_OK; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::scrollTo(enum IA2ScrollType /*scrollType*/) -{ - //### Ignore for now - return E_NOTIMPL; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::scrollToPoint(enum IA2CoordinateType /*coordinateType*/, long /*x*/, long /*y*/) -{ - //### Ignore for now - return E_NOTIMPL; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_groupPosition(long *groupLevel, - long *similarItemsInGroup, - long *positionInGroup) -{ - // ### Ignore for now. Not sure what this is used for..... - *groupLevel = 0; // Not applicable - *similarItemsInGroup = 0; // Not applicable - *positionInGroup = 0; // Not applicable - return S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_states(AccessibleStates *states) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - if (!states) - return E_POINTER; - QAccessible::State st = accessible->state(); - AccessibleStates ia2states = 0; - if (st.active) - ia2states |= IA2_STATE_ACTIVE; - if (st.invalid) - ia2states |= IA2_STATE_DEFUNCT; - if (st.editable) - ia2states |= IA2_STATE_EDITABLE; - if (st.multiLine) - ia2states |= IA2_STATE_MULTI_LINE; - if (st.selectableText) - ia2states |= IA2_STATE_SELECTABLE_TEXT; - if (st.supportsAutoCompletion) - ia2states |= IA2_STATE_SUPPORTS_AUTOCOMPLETION; - - *states = ia2states; - return S_OK; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_extendedRole(BSTR *extendedRole) -{ - //### - *extendedRole = 0; - return E_NOTIMPL; // mozilla does this - //return S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_localizedExtendedRole(BSTR *localizedExtendedRole) -{ - //### - *localizedExtendedRole = 0; - return E_NOTIMPL; // mozilla does this - //return S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nExtendedStates(long *nExtendedStates) -{ - // Who will ever intepret these values into something meaningful?? - *nExtendedStates = 0; - return E_NOTIMPL; // mozilla does this - //return S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_extendedStates(long /*maxExtendedStates*/, - BSTR **extendedStates, - long *nExtendedStates) -{ - *extendedStates = 0; - *nExtendedStates = 0; - return E_NOTIMPL; // mozilla does this - //return S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_localizedExtendedStates(long /*maxLocalizedExtendedStates*/, - BSTR **localizedExtendedStates, - long *nLocalizedExtendedStates) -{ - *localizedExtendedStates = 0; - *nLocalizedExtendedStates = 0; - return E_NOTIMPL; // mozilla does this - //return S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_uniqueID(long *outUniqueID) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - qCDebug(lcQpaAccessibility) << "uniqueID: " << showbase << hex << id; - - *outUniqueID = (long)id; - return int(id) < 0 ? S_OK : S_FALSE; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_windowHandle(HWND *windowHandle) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - return GetWindow(windowHandle); -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_indexInParent(long *indexInParent) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - if (!indexInParent) - return E_INVALIDARG; - QAccessibleInterface *par = accessible->parent(); - *indexInParent = par ? par->indexOfChild(accessible) : -1; - if (*indexInParent < 0) { - qCWarning(lcQpaAccessibility) << "index in parent invalid:" << accessible << "parent:" << par; - return S_FALSE; - } - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_locale(IA2Locale *locale) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - IA2Locale res; - QLocale l; - res.country = QStringToBSTR(QLocale::countryToString(l.country())); - res.language = QStringToBSTR(QLocale::languageToString(l.language())); - res.variant = QStringToBSTR(QString()); - *locale = res; - return S_OK; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_attributes(BSTR *attributes) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *attributes = 0;//QStringToBSTR(QString()); - return S_FALSE; -} - -/**************************************************************\ - * IAccessibleAction * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::nActions(long *nActions) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *nActions = QAccessibleBridgeUtils::effectiveActionNames(accessible).count(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::doAction(long actionIndex) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(accessible); - if (actionIndex < 0 || actionIndex >= actionNames.count()) - return E_INVALIDARG; - const QString actionName = actionNames.at(actionIndex); - return QAccessibleBridgeUtils::performEffectiveAction(accessible, actionName) ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_description(long actionIndex, BSTR *description) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *description = 0; - const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(accessible); - if (actionIndex < 0 || actionIndex >= actionNames.count()) - return E_INVALIDARG; - const QString actionName = actionNames.at(actionIndex); - if (QAccessibleActionInterface *actionIface = actionInterface()) - *description = QStringToBSTR(actionIface->localizedActionDescription(actionName)); - else - *description = QStringToBSTR(qAccessibleLocalizedActionDescription(actionName)); - - return *description ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_keyBinding(long actionIndex, long nMaxBindings, BSTR **keyBindings, long *nBindings) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - Q_UNUSED(nMaxBindings); - BSTR *arrayOfBindingsToReturn = 0; - int numBindings = 0; - if (QAccessibleActionInterface *actionIface = actionInterface()) { - const QStringList actionNames = actionIface->actionNames(); - if (actionIndex < 0 || actionIndex >= actionNames.count()) - return E_INVALIDARG; - const QString actionName = actionNames.at(actionIndex); - const QStringList keyBindings = actionIface->keyBindingsForAction(actionName); - numBindings = keyBindings.count(); - if (numBindings > 0) { - // The IDL documents that the client must free with CoTaskMemFree - arrayOfBindingsToReturn = coTaskMemAllocArray<BSTR>(numBindings); - std::transform(keyBindings.constBegin(), keyBindings.constEnd(), - QT_MAKE_CHECKED_ARRAY_ITERATOR(arrayOfBindingsToReturn, numBindings), - QStringToBSTR); - } - } - *keyBindings = arrayOfBindingsToReturn; - *nBindings = numBindings; - - return numBindings ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_name(long actionIndex, BSTR *name) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *name = 0; - const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(accessible); - if (actionIndex < 0 || actionIndex >= actionNames.count()) - return E_INVALIDARG; - const QString actionName = actionNames.at(actionIndex); - *name = QStringToBSTR(actionName); - return *name ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_localizedName(long actionIndex, BSTR *localizedName) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *localizedName = 0; - const QStringList actionNames = QAccessibleBridgeUtils::effectiveActionNames(accessible); - if (actionIndex < 0 || actionIndex >= actionNames.count()) - return E_INVALIDARG; - - const QString actionName = actionNames.at(actionIndex); - if (QAccessibleActionInterface *actionIface = actionInterface()) - *localizedName = QStringToBSTR(actionIface->localizedActionName(actionName)); - else - *localizedName = QStringToBSTR(QAccessibleActionInterface::tr(qPrintable(actionName))); - - return *localizedName ? S_OK : S_FALSE; -} - -/**************************************************************\ - * IAccessibleComponent * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_locationInParent(long *x, long *y) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QPoint topLeft = accessible->rect().topLeft(); - - QAccessibleInterface *parentIface = accessible->parent(); - if (parentIface && parentIface->isValid()) - topLeft -= parentIface->rect().topLeft(); - const QPoint nativeTopLeft = QHighDpi::toNativeLocalPosition(topLeft, accessible->window()); - - - *x = nativeTopLeft.x(); - *y = nativeTopLeft.y(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_foreground(IA2Color *foreground) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - // IA2Color is a typedef for long - *foreground = static_cast<IA2Color>(accessible->foregroundColor().rgb()); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_background(IA2Color *background) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - // IA2Color is a typedef for long - *background = static_cast<IA2Color>(accessible->backgroundColor().rgb()); - return S_OK; -} - -/**************************************************************\ - * IAccessibleEditableText * - **************************************************************/ -#if QT_CONFIG(clipboard) -/*! - \internal - - if \a endOffset == -1 it means end of the text -*/ -QString QWindowsIA2Accessible::textForRange(int startOffset, int endOffset) const -{ - QAccessibleInterface *accessible = accessibleInterface(); - - if (QAccessibleTextInterface *textIface = accessible->textInterface()) { - if (endOffset == IA2_TEXT_OFFSET_LENGTH) - endOffset = textIface->characterCount(); - return textIface->text(startOffset, endOffset); - } - QString txt = accessible->text(QAccessible::Value); - if (endOffset == IA2_TEXT_OFFSET_LENGTH) - endOffset = txt.length(); - return txt.mid(startOffset, endOffset - startOffset); -} -#endif - -/*! - \internal -*/ -void QWindowsIA2Accessible::replaceTextFallback(long startOffset, long endOffset, const QString &txt) -{ - QAccessibleInterface *accessible = accessibleInterface(); - QString t = textForRange(0, -1); - if (endOffset == IA2_TEXT_OFFSET_LENGTH) - endOffset = t.length(); - if (endOffset - startOffset == 0) { - t.insert(startOffset, txt); - } else { - t.replace(startOffset, endOffset - startOffset, txt); - } - accessible->setText(QAccessible::Value, t); -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::copyText(long startOffset, long endOffset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); -#if QT_CONFIG(clipboard) - const QString t = textForRange(startOffset, endOffset); - QGuiApplication::clipboard()->setText(t); - return S_OK; -#else - return E_NOTIMPL; -#endif -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::deleteText(long startOffset, long endOffset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleEditableTextInterface *editableTextIface = accessible->editableTextInterface()) - editableTextIface->deleteText(startOffset, endOffset); - else - replaceTextFallback(startOffset, endOffset, QString()); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::insertText(long offset, BSTR *text) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - const QString txt = QString::fromWCharArray(*text); - if (QAccessibleEditableTextInterface *editableTextIface = accessible->editableTextInterface()) - editableTextIface->insertText(offset, txt); - else - replaceTextFallback(offset, offset, txt); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::cutText(long startOffset, long endOffset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); -#if QT_CONFIG(clipboard) - const QString t = textForRange(startOffset, endOffset); - if (QAccessibleEditableTextInterface *editableTextIface = accessible->editableTextInterface()) - editableTextIface->deleteText(startOffset, endOffset); - else - replaceTextFallback(startOffset, endOffset, QString()); - QGuiApplication::clipboard()->setText(t); - return S_OK; -#else - return E_NOTIMPL; -#endif -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::pasteText(long offset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); -#if QT_CONFIG(clipboard) - const QString txt = QGuiApplication::clipboard()->text(); - if (QAccessibleEditableTextInterface *editableTextIface = accessible->editableTextInterface()) - editableTextIface->insertText(offset, txt); - else - replaceTextFallback(offset, offset, txt); - return S_OK; -#else - return E_NOTIMPL; -#endif -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::replaceText(long startOffset, long endOffset, BSTR *text) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - const QString txt = QString::fromWCharArray(*text); - if (QAccessibleEditableTextInterface *editableTextIface = accessible->editableTextInterface()) - editableTextIface->replaceText(startOffset, endOffset, txt); - else - replaceTextFallback(startOffset, endOffset, txt); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::setAttributes(long /*startOffset*/, long /*endOffset*/, BSTR * /*attributes*/) -{ - return E_NOTIMPL; -} - - -/**************************************************************\ - * IAccessibleTable2 * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_cellAt( long row, long column, IUnknown **cell) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *cell = 0; - if (QAccessibleTableInterface *tableIface = tableInterface()) { - if (QAccessibleInterface *qtCell = tableIface->cellAt(row, column)) { - *cell = QWindowsAccessibility::wrap(qtCell); - } - } - qCDebug(lcQpaAccessibility) << "found cell? " << *cell; - return *cell ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_caption( IUnknown **captionInterface) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *captionInterface = 0; - if (QAccessibleTableInterface *tableIface = tableInterface()) { - if (QAccessibleInterface *iface = tableIface->caption()) - *captionInterface = QWindowsAccessibility::wrap(iface); - } - return *captionInterface ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_columnDescription( long column, BSTR *description) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *description = 0; - if (QAccessibleTableInterface *tableIface = tableInterface()) { - const QString qtDesc = tableIface->columnDescription(column); - if (!qtDesc.isEmpty()) - *description = QStringToBSTR(qtDesc); - } - return *description ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nColumns( long *columnCount) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *columnCount = tableIface->columnCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nRows(long *rowCount) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *rowCount = tableIface->rowCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nSelectedCells(long *cellCount) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *cellCount = tableIface->selectedCellCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nSelectedColumns(long *columnCount) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *columnCount = tableIface->selectedColumnCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nSelectedRows(long *rowCount) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *rowCount = tableIface->selectedRowCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_rowDescription(long row, BSTR *description) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *description = 0; - if (QAccessibleTableInterface *tableIface = tableInterface()) { - const QString qtDesc = tableIface->rowDescription(row); - if (!qtDesc.isEmpty()) - *description = QStringToBSTR(qtDesc); - } - return *description ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_selectedCells(IUnknown ***cells, long *nSelectedCells) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - Q_UNUSED(cells); - Q_UNUSED(nSelectedCells); - if (!accessible) - return E_FAIL; - - QList<QAccessibleInterface*> selectedCells = tableInterface()->selectedCells(); - return wrapListOfCells(selectedCells, cells, nSelectedCells); -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_selectedColumns(long **selectedColumns, long *nColumns) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - const QList<int> selectedIndices = tableIface->selectedColumns(); - const int count = selectedIndices.count(); - *nColumns = count; - *selectedColumns = Q_NULLPTR; - if (count) { - *selectedColumns = coTaskMemAllocArray<long>(count); - std::copy(selectedIndices.constBegin(), selectedIndices.constEnd(), - QT_MAKE_CHECKED_ARRAY_ITERATOR(*selectedColumns, count)); - } - return count ? S_OK : S_FALSE; - } - return E_FAIL; - -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_selectedRows(long **selectedRows, long *nRows) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - const QList<int> selectedIndices = tableIface->selectedRows(); - const int count = selectedIndices.count(); - *nRows = count; - *selectedRows = Q_NULLPTR; - if (count) { - *selectedRows = coTaskMemAllocArray<long>(count); - std::copy(selectedIndices.constBegin(), selectedIndices.constEnd(), - QT_MAKE_CHECKED_ARRAY_ITERATOR(*selectedRows, count)); - } - return count ? S_OK : S_FALSE; - } - return E_FAIL; - -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_summary(IUnknown **summaryInterface) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *summaryInterface = 0; - if (QAccessibleTableInterface *tableIface = tableInterface()) { - if (QAccessibleInterface *iface = tableIface->summary()) - *summaryInterface = QWindowsAccessibility::wrap(iface); - } - return *summaryInterface ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_isColumnSelected(long column, boolean *isSelected) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *isSelected = tableIface->isColumnSelected(column); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_isRowSelected(long row, boolean *isSelected) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - *isSelected = tableIface->isRowSelected(row); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::selectRow(long row) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - bool ok = tableIface->selectRow(row); - return ok ? S_OK : E_INVALIDARG; //### Not sure of the return value if it fails??? - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::selectColumn(long column) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - bool ok = tableIface->selectColumn(column); - return ok ? S_OK : E_INVALIDARG; //### Not sure of the return value if it fails??? - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::unselectRow(long row) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - bool ok = tableIface->unselectRow(row); - return ok ? S_OK : E_INVALIDARG; //### Not sure of the return value if it fails??? - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::unselectColumn(long column) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleTableInterface *tableIface = tableInterface()) { - bool ok = tableIface->unselectColumn(column); - return ok ? S_OK : E_INVALIDARG; //### Not sure of the return value if it fails??? - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_modelChange( IA2TableModelChange * /*modelChange*/) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - return E_NOTIMPL; -} - -/**************************************************************\ - * IAccessibleTableCell * -\**************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_columnExtent(long *nColumnsSpanned) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *nColumnsSpanned = tableCellInterface()->columnExtent(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_columnHeaderCells(IUnknown ***cellAccessibles, - long *nColumnHeaderCells) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - const QList<QAccessibleInterface*> headerCells = tableCellInterface()->columnHeaderCells(); - return wrapListOfCells(headerCells, cellAccessibles, nColumnHeaderCells); -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_columnIndex(long *columnIndex) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *columnIndex = tableCellInterface()->columnIndex(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_rowExtent(long *nRowsSpanned) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *nRowsSpanned = tableCellInterface()->rowExtent(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_rowHeaderCells(IUnknown ***cellAccessibles, - long *nRowHeaderCells) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - const QList<QAccessibleInterface*> headerCells = tableCellInterface()->rowHeaderCells(); - return wrapListOfCells(headerCells, cellAccessibles, nRowHeaderCells); -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_rowIndex(long *rowIndex) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *rowIndex = tableCellInterface()->rowIndex(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_isSelected( boolean *isSelected) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - *isSelected = tableCellInterface()->isSelected(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_rowColumnExtents(long *row, long *column, - long *rowExtents, long *columnExtents, - boolean *isSelected) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible || !tableCellInterface()) - return E_FAIL; - - *row = tableCellInterface()->rowIndex(); - *column = tableCellInterface()->columnIndex(); - *rowExtents = tableCellInterface()->rowExtent(); - *columnExtents = tableCellInterface()->columnExtent(); - *isSelected = tableCellInterface()->isSelected(); - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_table(IUnknown **table) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QAccessibleInterface *tableIface = tableCellInterface()->table(); - - *table = QWindowsAccessibility::wrap(tableIface); - return S_OK; -} - -/**************************************************************\ - * IAccessibleText * -\**************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::addSelection(long startOffset, - long endOffset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - text->addSelection(startOffset, endOffset); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_attributes(long offset, - long *startOffset, - long *endOffset, - BSTR *textAttributes) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - const QString attrs = text->attributes(offset, reinterpret_cast<int *>(startOffset), - reinterpret_cast<int *>(endOffset)); - *textAttributes = QStringToBSTR(attrs); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_caretOffset(long *offset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - *offset = text->cursorPosition(); - return S_OK; - } - return E_FAIL; -} - - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_characterExtents(long offset, - enum IA2CoordinateType coordType, - long *x, - long *y, - long *width, - long *height) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - QRect rect = text->characterRect(offset); - mapFromScreenPos(coordType, rect.topLeft(), x, y); - *width = rect.width(); - *height = rect.height(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nSelections(long *nSelections) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - *nSelections = text->selectionCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_offsetAtPoint(long x, - long y, - enum IA2CoordinateType coordType, - long *offset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - QPoint screenPos = mapToScreenPos(coordType, x, y); - *offset = text->offsetAtPoint(screenPos); - return (*offset >=0 ? S_OK : S_FALSE); - } - return E_FAIL; - -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_selection(long selectionIndex, - long *startOffset, - long *endOffset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *text = textInterface()) { - text->selection(selectionIndex, reinterpret_cast<int *>(startOffset), - reinterpret_cast<int *>(endOffset)); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_text(long startOffset, - long endOffset, - BSTR *text) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textif = textInterface()) { - const QString t = textif->text(startOffset, endOffset); - if (!t.isEmpty()) { - *text = QStringToBSTR(t); - return S_OK; - } - return E_INVALIDARG; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_textBeforeOffset(long offset, - enum IA2TextBoundaryType boundaryType, - long *startOffset, - long *endOffset, - BSTR *text) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - const QString txt = - textIface->textBeforeOffset(offset, static_cast<QAccessible::TextBoundaryType>(boundaryType), - reinterpret_cast<int *>(startOffset), - reinterpret_cast<int *>(endOffset)); - if (!txt.isEmpty()) { - *text = QStringToBSTR(txt); - return S_OK; - } - return S_FALSE; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_textAfterOffset( - long offset, - enum IA2TextBoundaryType boundaryType, - long *startOffset, - long *endOffset, - BSTR *text) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - const QString txt = - textIface->textAfterOffset(offset, static_cast<QAccessible::TextBoundaryType>(boundaryType), - reinterpret_cast<int *>(startOffset), - reinterpret_cast<int *>(endOffset)); - if (!txt.isEmpty()) { - *text = QStringToBSTR(txt); - return S_OK; - } - return S_FALSE; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_textAtOffset(long offset, - enum IA2TextBoundaryType boundaryType, - long *startOffset, - long *endOffset, - BSTR *text) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - const QString txt = - textIface->textAtOffset(offset, static_cast<QAccessible::TextBoundaryType>(boundaryType), - reinterpret_cast<int *>(startOffset), - reinterpret_cast<int *>(endOffset)); - if (!txt.isEmpty()) { - *text = QStringToBSTR(txt); - return S_OK; - } - return S_FALSE; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::removeSelection(long selectionIndex) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - textIface->removeSelection(selectionIndex); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::setCaretOffset(long offset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - textIface->setCursorPosition(offset); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::setSelection(long selectionIndex, - long startOffset, - long endOffset) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - textIface->setSelection(selectionIndex, startOffset, endOffset); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_nCharacters(long *nCharacters) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - *nCharacters = textIface->characterCount(); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::scrollSubstringTo(long startIndex, - long endIndex, - enum IA2ScrollType scrollType) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (QAccessibleTextInterface *textIface = textInterface()) { - Q_UNUSED(scrollType); //### - textIface->scrollToSubstring(startIndex, endIndex); - return S_OK; - } - return E_FAIL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::scrollSubstringToPoint(long startIndex, - long endIndex, - enum IA2CoordinateType coordinateType, - long x, - long y) -{ - Q_UNUSED(startIndex); - Q_UNUSED(endIndex); - Q_UNUSED(coordinateType); - Q_UNUSED(x); - Q_UNUSED(y); - - return E_NOTIMPL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_newText(IA2TextSegment *newText) -{ - Q_UNUSED(newText); - return E_NOTIMPL; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_oldText(IA2TextSegment *oldText) -{ - Q_UNUSED(oldText); - return E_NOTIMPL; -} - -/**************************************************************\ - * IAccessibleValue * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_currentValue(VARIANT *currentValue) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - if (QAccessibleValueInterface *valueIface = valueInterface()) { - const QVariant var = valueIface->currentValue(); - if (QVariant2VARIANT(var, *currentValue, QByteArray(), false)) - return S_OK; - - } - currentValue->vt = VT_EMPTY; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::setCurrentValue(VARIANT value) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - HRESULT hr = S_FALSE; - if (QAccessibleValueInterface *valueIface = valueInterface()) { - hr = VariantChangeType(&value, &value, 0, VT_R8); - if (SUCCEEDED(hr)) { - // ### works only for numbers (not date, strings, etc) - valueIface->setCurrentValue(QVariant(value.dblVal)); - } - } - return hr; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_maximumValue(VARIANT *maximumValue) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - if (QAccessibleValueInterface *valueIface = valueInterface()) { - const QVariant var = valueIface->maximumValue(); - if (QVariant2VARIANT(var, *maximumValue, QByteArray(), false)) - return S_OK; - } - maximumValue->vt = VT_EMPTY; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::get_minimumValue(VARIANT *minimumValue) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - if (QAccessibleValueInterface *valueIface = valueInterface()) { - const QVariant var = valueIface->minimumValue(); - if (QVariant2VARIANT(var, *minimumValue, QByteArray(), false)) - return S_OK; - } - minimumValue->vt = VT_EMPTY; - return S_FALSE; -} - - -/**************************************************************\ - * IServiceProvider * - **************************************************************/ -/*! - \internal - Reimplemented from IServiceProvider -*/ -HRESULT STDMETHODCALLTYPE QWindowsIA2Accessible::QueryService(REFGUID guidService, REFIID riid, void **iface) -{ - if (!iface) - return E_POINTER; - Q_UNUSED(guidService); - *iface = 0; - qCDebug(lcQpaAccessibility) << "QWindowsIA2Accessible::QS(): " << IIDToString(riid); - - - if (guidService == IID_IAccessible) { - if (riid == IID_IServiceProvider) { - // do not end up calling QueryInterface for IID_IServiceProvider - *iface = 0; - } else if (riid == IID_IAccessible || riid == IID_IUnknown || riid == IID_IDispatch) { - // The above conditions works with AccProbe and NVDA. - *iface = static_cast<IAccessible*>(this); - } else { - // According to _dicoveringInterfaces Discovery of Interfaces, we should really only - // enter here if riid == IID_IAccessible2, but some screen readers does not like that, - // and other servers seems to have realized that. (Chrome and Mozilla for instance, - // calls QueryInterface more or less in the same way) - - // For instance, accProbe discovers IID_IAccessibleTable2 by a QueryService only. - return QueryInterface(riid, iface); - } - } - - if (riid == IID_IAccessibleApplication) { - *iface = new AccessibleApplication; - return S_OK; - } - if (*iface) { - AddRef(); - return S_OK; - } - - return E_NOINTERFACE; -} - - -/*! - \internal - private function.. - \a maxRelations max number of relations to return in \a relations - \a relations the array of relations matching - \a startIndex Index to start to return from, - it will return only that specific relation in \a relations - - If \a relations is null, \a startIndex and \a maxRelations are ignored, causing - it to return the number of relations in \a nRelations -*/ -HRESULT QWindowsIA2Accessible::getRelationsHelper(IAccessibleRelation **relations, int startIndex, long maxRelations, long *nRelations /* = 0*/) -{ - QAccessibleInterface *accessible = accessibleInterface(); - if (nRelations) - *nRelations = 0; - typedef QPair<QAccessibleInterface *, QAccessible::Relation> RelationEntry; - QVector<RelationEntry> rels = accessible->relations(); - QMap<QAccessible::Relation, QAccessibleInterface *> relationMap; - for (QVector<RelationEntry>::const_iterator it = rels.constBegin(); it != rels.constEnd(); ++it) - { - RelationEntry e = *it; - relationMap.insertMulti(e.second, e.first); - } - - QList<QAccessible::Relation> keys = relationMap.keys(); - const int numRelations = keys.count(); - if (relations) { - for (int i = startIndex; i < qMin(startIndex + int(maxRelations), numRelations); ++i) { - QAccessible::Relation relation = keys.at(i); - QList<QAccessibleInterface*> targets = relationMap.values(relation); - AccessibleRelation *rel = new AccessibleRelation(targets, relation); - *relations = rel; - ++relations; - } - } - if (nRelations) - *nRelations = numRelations; - - return numRelations > 0 ? S_OK : S_FALSE; -} - - - - -/*! - \internal - helper to wrap a QList<QAccessibleInterface*> inside an array of IAccessible* - The IAccessible* array is returned as a IUnknown* -*/ -HRESULT QWindowsIA2Accessible::wrapListOfCells(const QList<QAccessibleInterface*> &inputCells, IUnknown ***outputAccessibles, long *nCellCount) -{ - const int count = inputCells.count(); - // Server allocates array - *nCellCount = count; - *outputAccessibles = Q_NULLPTR; - if (count) { - *outputAccessibles = coTaskMemAllocArray<IUnknown *>(count); - std::transform(inputCells.constBegin(), inputCells.constEnd(), - QT_MAKE_CHECKED_ARRAY_ITERATOR(*outputAccessibles, count), - QWindowsAccessibility::wrap); - } - 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)); -Q_STATIC_ASSERT(IA2_ROLE_FORM == static_cast<IA2Role>(QAccessible::Form)); -Q_STATIC_ASSERT(IA2_ROLE_HEADING == static_cast<IA2Role>(QAccessible::Heading)); -Q_STATIC_ASSERT(IA2_ROLE_NOTE == static_cast<IA2Role>(QAccessible::Note)); -Q_STATIC_ASSERT(IA2_ROLE_COMPLEMENTARY_CONTENT == static_cast<IA2Role>(QAccessible::ComplementaryContent)); - -QT_END_NAMESPACE - -#endif //QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/accessible/iaccessible2.h b/src/plugins/platforms/windows/accessible/iaccessible2.h deleted file mode 100644 index bc5f5be60f..0000000000 --- a/src/plugins/platforms/windows/accessible/iaccessible2.h +++ /dev/null @@ -1,351 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 IACCESSIBLE2_H -#define IACCESSIBLE2_H - -#include <QtCore/QtConfig> -#ifndef QT_NO_ACCESSIBILITY - -#include "qwindowsmsaaaccessible.h" -#include "comutils.h" - -#include "ia2_api_all.h" - -#include <servprov.h> - -QT_BEGIN_NAMESPACE - -class QWindowsIA2Accessible : public QWindowsMsaaAccessible, - public IAccessibleAction, - public IAccessibleComponent, - public IAccessibleEditableText, - public IAccessibleTable2, - public IAccessibleTableCell, - public IAccessibleText, - public IAccessibleValue, - public IServiceProvider -{ -public: - QWindowsIA2Accessible(QAccessibleInterface *a) : QWindowsMsaaAccessible(a) {} - - /* IUnknown */ - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - - /* IAccessible2 */ - HRESULT STDMETHODCALLTYPE get_nRelations(long *nRelations); - HRESULT STDMETHODCALLTYPE get_relation(long relationIndex, IAccessibleRelation **relation); - HRESULT STDMETHODCALLTYPE get_relations(long maxRelations, IAccessibleRelation **relations, long *nRelations); - HRESULT STDMETHODCALLTYPE role(long *role); - HRESULT STDMETHODCALLTYPE scrollTo(enum IA2ScrollType scrollType); - HRESULT STDMETHODCALLTYPE scrollToPoint(enum IA2CoordinateType coordinateType, long x, long y); - HRESULT STDMETHODCALLTYPE get_groupPosition(long *groupLevel, long *similarItemsInGroup, long *positionInGroup); - HRESULT STDMETHODCALLTYPE get_states(AccessibleStates *states); - HRESULT STDMETHODCALLTYPE get_extendedRole(BSTR *extendedRole); - HRESULT STDMETHODCALLTYPE get_localizedExtendedRole(BSTR *localizedExtendedRole); - HRESULT STDMETHODCALLTYPE get_nExtendedStates(long *nExtendedStates); - HRESULT STDMETHODCALLTYPE get_extendedStates(long maxExtendedStates, BSTR **extendedStates, long *nExtendedStates); - HRESULT STDMETHODCALLTYPE get_localizedExtendedStates(long maxLocalizedExtendedStates, BSTR **localizedExtendedStates, long *nLocalizedExtendedStates); - HRESULT STDMETHODCALLTYPE get_uniqueID(long *uniqueID); - HRESULT STDMETHODCALLTYPE get_windowHandle(HWND *windowHandle); - HRESULT STDMETHODCALLTYPE get_indexInParent(long *indexInParent); - HRESULT STDMETHODCALLTYPE get_locale(IA2Locale *locale); - HRESULT STDMETHODCALLTYPE get_attributes(BSTR *attributes); - - /* IAccessibleAction */ - HRESULT STDMETHODCALLTYPE nActions(long *nActions); - HRESULT STDMETHODCALLTYPE doAction(long actionIndex); - HRESULT STDMETHODCALLTYPE get_description(long actionIndex, BSTR *description); - HRESULT STDMETHODCALLTYPE get_keyBinding(long actionIndex, long nMaxBindings, BSTR **keyBindings, long *nBindings); - HRESULT STDMETHODCALLTYPE get_name(long actionIndex, BSTR *name); - HRESULT STDMETHODCALLTYPE get_localizedName(long actionIndex, BSTR *localizedName); - - /* IAccessibleComponent */ - HRESULT STDMETHODCALLTYPE get_locationInParent(long *x,long *y); - HRESULT STDMETHODCALLTYPE get_foreground(IA2Color *foreground); - HRESULT STDMETHODCALLTYPE get_background(IA2Color *background); - - /* IAccessibleEditableText */ - HRESULT STDMETHODCALLTYPE copyText(long startOffset, long endOffset); - HRESULT STDMETHODCALLTYPE deleteText(long startOffset, long endOffset); - HRESULT STDMETHODCALLTYPE insertText(long offset, BSTR *text); - HRESULT STDMETHODCALLTYPE cutText(long startOffset, long endOffset); - HRESULT STDMETHODCALLTYPE pasteText(long offset); - HRESULT STDMETHODCALLTYPE replaceText(long startOffset, long endOffset, BSTR *text); - HRESULT STDMETHODCALLTYPE setAttributes(long startOffset, long endOffset, BSTR *attributes); - - /* IAccessibleTable2 */ - HRESULT STDMETHODCALLTYPE get_cellAt( long row, long column, IUnknown **cell); - HRESULT STDMETHODCALLTYPE get_caption( IUnknown **accessibleInterface); - HRESULT STDMETHODCALLTYPE get_columnDescription( long column, BSTR *description); - HRESULT STDMETHODCALLTYPE get_nColumns( long *columnCount); - HRESULT STDMETHODCALLTYPE get_nRows( long *rowCount); - HRESULT STDMETHODCALLTYPE get_nSelectedCells( long *cellCount); - HRESULT STDMETHODCALLTYPE get_nSelectedColumns( long *columnCount); - HRESULT STDMETHODCALLTYPE get_nSelectedRows( long *rowCount); - HRESULT STDMETHODCALLTYPE get_rowDescription( long row, BSTR *description); - HRESULT STDMETHODCALLTYPE get_selectedCells( IUnknown ***cells, long *nSelectedCells); - HRESULT STDMETHODCALLTYPE get_selectedColumns( long **selectedColumns, long *nColumns); - HRESULT STDMETHODCALLTYPE get_selectedRows( long **selectedRows, long *nRows); - HRESULT STDMETHODCALLTYPE get_summary( IUnknown **accessibleInterface); - HRESULT STDMETHODCALLTYPE get_isColumnSelected( long column, boolean *isSelected); - HRESULT STDMETHODCALLTYPE get_isRowSelected( long row, boolean *isSelected); - HRESULT STDMETHODCALLTYPE selectRow( long row); - HRESULT STDMETHODCALLTYPE selectColumn( long column); - HRESULT STDMETHODCALLTYPE unselectRow( long row); - HRESULT STDMETHODCALLTYPE unselectColumn( long column); - HRESULT STDMETHODCALLTYPE get_modelChange( IA2TableModelChange *modelChange); - - /* IAccessibleTableCell */ - HRESULT STDMETHODCALLTYPE get_columnExtent(long *nColumnsSpanned); - HRESULT STDMETHODCALLTYPE get_columnHeaderCells(IUnknown ***cellAccessibles, long *nColumnHeaderCells); - HRESULT STDMETHODCALLTYPE get_columnIndex(long *columnIndex); - HRESULT STDMETHODCALLTYPE get_rowExtent(long *nRowsSpanned); - HRESULT STDMETHODCALLTYPE get_rowHeaderCells(IUnknown ***cellAccessibles, long *nRowHeaderCells); - HRESULT STDMETHODCALLTYPE get_rowIndex(long *rowIndex); - HRESULT STDMETHODCALLTYPE get_isSelected( boolean *isSelected); - HRESULT STDMETHODCALLTYPE get_rowColumnExtents(long *row, long *column, - long *rowExtents, long *columnExtents, - boolean *isSelected); - HRESULT STDMETHODCALLTYPE get_table(IUnknown **table); - - - /* IAccessibleText */ - HRESULT STDMETHODCALLTYPE addSelection(long startOffset, long endOffset); - HRESULT STDMETHODCALLTYPE get_attributes(long offset, long *startOffset, - long *endOffset, BSTR *textAttributes); - HRESULT STDMETHODCALLTYPE get_caretOffset(long *offset); - HRESULT STDMETHODCALLTYPE get_characterExtents(long offset, enum IA2CoordinateType coordType, - long *x, long *y, - long *width, long *height); - HRESULT STDMETHODCALLTYPE get_nSelections(long *nSelections); - HRESULT STDMETHODCALLTYPE get_offsetAtPoint(long x, long y, enum IA2CoordinateType coordType, long *offset); - HRESULT STDMETHODCALLTYPE get_selection(long selectionIndex, long *startOffset, long *endOffset); - HRESULT STDMETHODCALLTYPE get_text(long startOffset, long endOffset, BSTR *text); - HRESULT STDMETHODCALLTYPE get_textBeforeOffset(long offset, enum IA2TextBoundaryType boundaryType, - long *startOffset, long *endOffset, BSTR *text); - HRESULT STDMETHODCALLTYPE get_textAfterOffset(long offset, enum IA2TextBoundaryType boundaryType, - long *startOffset, long *endOffset, BSTR *text); - HRESULT STDMETHODCALLTYPE get_textAtOffset(long offset, enum IA2TextBoundaryType boundaryType, - long *startOffset, long *endOffset, BSTR *text); - HRESULT STDMETHODCALLTYPE removeSelection(long selectionIndex); - HRESULT STDMETHODCALLTYPE setCaretOffset(long offset); - HRESULT STDMETHODCALLTYPE setSelection(long selectionIndex, long startOffset, long endOffset); - HRESULT STDMETHODCALLTYPE get_nCharacters(long *nCharacters); - HRESULT STDMETHODCALLTYPE scrollSubstringTo(long startIndex, long endIndex, enum IA2ScrollType scrollType); - HRESULT STDMETHODCALLTYPE scrollSubstringToPoint(long startIndex, long endIndex, - enum IA2CoordinateType coordinateType, long x, long y); - HRESULT STDMETHODCALLTYPE get_newText(IA2TextSegment *newText); - HRESULT STDMETHODCALLTYPE get_oldText(IA2TextSegment *oldText); - - /* IAccessibleValue */ - HRESULT STDMETHODCALLTYPE get_currentValue(VARIANT *currentValue); - HRESULT STDMETHODCALLTYPE setCurrentValue(VARIANT value); - HRESULT STDMETHODCALLTYPE get_maximumValue(VARIANT *maximumValue); - HRESULT STDMETHODCALLTYPE get_minimumValue(VARIANT *minimumValue); - - /* IServiceProvider */ - HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService, REFIID riid, void **ppv); - - /* private helper functions */ -private: - inline QAccessibleTextInterface *textInterface() const { - QAccessibleInterface *accessible = accessibleInterface(); - return accessible ? accessible->textInterface() : static_cast<QAccessibleTextInterface *>(0); - } - - inline QAccessibleActionInterface *actionInterface() const { - QAccessibleInterface *accessible = accessibleInterface(); - return accessible->actionInterface(); - } - - inline QAccessibleValueInterface *valueInterface() const { - QAccessibleInterface *accessible = accessibleInterface(); - return accessible->valueInterface(); - } - - inline QAccessibleTableInterface *tableInterface() const { - QAccessibleInterface *accessible = accessibleInterface(); - return accessible->tableInterface(); - } - - inline QAccessibleTableCellInterface *tableCellInterface() const { - QAccessibleInterface *accessible = accessibleInterface(); - return accessible->tableCellInterface(); - } - - /*! - \internal - \a screenPos is in screen relative position - \a x and \y (out) is in parent relative position if coordType == IA2_COORDTYPE_PARENT_RELATIVE - */ - void mapFromScreenPos(enum IA2CoordinateType coordType, const QPoint &screenPos, long *x, long *y) const { - QAccessibleInterface *accessible = accessibleInterface(); - if (coordType == IA2_COORDTYPE_PARENT_RELATIVE) { - // caller wants relative to parent - if (QAccessibleInterface *parent = accessible->parent()) { - const QRect parentScreenRect = parent->rect(); - *x = parentScreenRect.x() - screenPos.x(); - *y = parentScreenRect.y() - screenPos.y(); - return; - } - } - *x = screenPos.x(); - *y = screenPos.y(); - } - - /*! - \internal - \a x and \y is in parent relative position if coordType == IA2_COORDTYPE_PARENT_RELATIVE - \return a screen relative position - */ - QPoint mapToScreenPos(enum IA2CoordinateType coordType, long x, long y) const { - QAccessibleInterface *accessible = accessibleInterface(); - if (coordType == IA2_COORDTYPE_PARENT_RELATIVE) { - if (QAccessibleInterface *parent = accessible->parent()) { - const QRect parentScreenRect = parent->rect(); - return QPoint(parentScreenRect.x() + x, parentScreenRect.y() + y); - } - } - return QPoint(x,y); - } - - 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); - -}; - -/**************************************************************\ - * AccessibleApplication * - **************************************************************/ -class AccessibleApplication : public IAccessibleApplication -{ -public: - AccessibleApplication() : m_ref(1) - { - - } - - 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; -}; - - - -/**************************************************************\ - * AccessibleRelation * - **************************************************************/ -class AccessibleRelation : public IAccessibleRelation -{ -public: - AccessibleRelation(const QList<QAccessibleInterface *> &targets, - QAccessible::Relation relation); - - 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); - HRESULT STDMETHODCALLTYPE get_nTargets(long *nTargets); - HRESULT STDMETHODCALLTYPE get_target(long targetIndex, IUnknown **target); - HRESULT STDMETHODCALLTYPE get_targets(long maxTargets, IUnknown **targets, long *nTargets); - -private: - static BSTR relationToBSTR(QAccessible::Relation relation) - { - const wchar_t *constRelationString = 0; - switch (relation) { - case QAccessible::Label: - constRelationString = IA2_RELATION_LABEL_FOR; - break; - case QAccessible::Labelled: - constRelationString = IA2_RELATION_LABELLED_BY; - break; - case QAccessible::Controller: - constRelationString = IA2_RELATION_CONTROLLER_FOR; - break; - case QAccessible::Controlled: - constRelationString = IA2_RELATION_CONTROLLED_BY; - break; - case QAccessible::AllRelations: - constRelationString = ( L"AllRelations" ); - break; - } - - if (constRelationString) { - BSTR bstrVal; - const UINT wlen = (UINT)wcslen(constRelationString); - bstrVal = ::SysAllocStringLen(constRelationString, wlen); - return bstrVal; - } - return 0; - } - - - QList<QAccessibleInterface *> m_targets; - QAccessible::Relation m_relation; - ULONG m_ref; -}; - -QT_END_NAMESPACE - -#endif //QT_NO_ACCESSIBILITY - -#endif // IACCESSIBLE2_H diff --git a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp b/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp deleted file mode 100644 index aed9c94003..0000000000 --- a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.cpp +++ /dev/null @@ -1,253 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QtCore/QtConfig> -#ifndef QT_NO_ACCESSIBILITY - - -#include <private/qsystemlibrary_p.h> - -#include <QtCore/qlocale.h> -#include <QtCore/qmap.h> -#include <QtCore/qpair.h> -#include <QtCore/qpointer.h> -#include <QtGui/qaccessible.h> -#include <QtGui/private/qguiapplication_p.h> -#include <qpa/qplatformnativeinterface.h> -#include <qpa/qplatformintegration.h> -#include <QtGui/qwindow.h> -#include <QtGui/qguiapplication.h> -#include <QtFontDatabaseSupport/private/qwindowsfontdatabase_p.h> // registry helper - -#include "qwindowsaccessibility.h" -#ifdef Q_CC_MINGW -# include "qwindowsmsaaaccessible.h" -#else -# include "iaccessible2.h" -#endif -#include "comutils.h" - -#include <oleacc.h> - -//#include <uiautomationcoreapi.h> -#ifndef UiaRootObjectId -#define UiaRootObjectId -25 -#endif - -#include <winuser.h> -#if !defined(WINABLEAPI) -# include <winable.h> -#endif - -#include <servprov.h> -#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) -#include <comdef.h> -#endif - -#include <QtCore/qt_windows.h> - -QT_BEGIN_NAMESPACE - -/*! - \!internal - \class QWindowsAccessibility - - Implements QPlatformAccessibility - -*/ -QWindowsAccessibility::QWindowsAccessibility() -{ -} - -// Retrieve sound name by checking the icon property of a message box -static inline QString messageBoxAlertSound(const QObject *messageBox) -{ - enum MessageBoxIcon { // Keep in sync with QMessageBox::Icon - Information = 1, - Warning = 2, - Critical = 3 - }; - switch (messageBox->property("icon").toInt()) { - case Information: - return QStringLiteral("SystemAsterisk"); - case Warning: - return QStringLiteral("SystemExclamation"); - case Critical: - return QStringLiteral("SystemHand"); - } - return QString(); -} - -static QString soundFileName(const QString &soundName) -{ - const QString key = QStringLiteral("AppEvents\\Schemes\\Apps\\.Default\\") - + soundName + QStringLiteral("\\.Current"); - return QWindowsFontDatabase::readRegistryString(HKEY_CURRENT_USER, - reinterpret_cast<const wchar_t *>(key.utf16()), L""); -} - -void QWindowsAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) -{ - QString soundName; - switch (event->type()) { - case QAccessible::PopupMenuStart: - soundName = QLatin1String("MenuPopup"); - break; - - case QAccessible::MenuCommand: - soundName = QLatin1String("MenuCommand"); - break; - - case QAccessible::Alert: - soundName = event->object()->inherits("QMessageBox") ? - messageBoxAlertSound(event->object()) : QStringLiteral("SystemAsterisk"); - break; - default: - break; - } - - if (!soundName.isEmpty() && !soundFileName(soundName).isEmpty()) { - PlaySound(reinterpret_cast<const wchar_t *>(soundName.utf16()), 0, - SND_ALIAS | SND_ASYNC | SND_NODEFAULT | SND_NOWAIT); - } - - // An event has to be associated with a window, - // so find the first parent that is a widget and that has a WId - QAccessibleInterface *iface = event->accessibleInterface(); - if (!isActive() || !iface || !iface->isValid()) - return; - QWindow *window = QWindowsAccessibility::windowHelper(iface); - - if (!window) { - window = QGuiApplication::focusWindow(); - if (!window) - return; - } - - QPlatformNativeInterface *platform = QGuiApplication::platformNativeInterface(); - if (!window->handle()) // Called before show(), no native window yet. - return; - const HWND hWnd = reinterpret_cast<HWND>(platform->nativeResourceForWindow("handle", window)); - - if (event->type() != QAccessible::MenuCommand && // MenuCommand is faked - event->type() != QAccessible::ObjectDestroyed) { - ::NotifyWinEvent(event->type(), hWnd, OBJID_CLIENT, QAccessible::uniqueId(iface)); - } -} - -QWindow *QWindowsAccessibility::windowHelper(const QAccessibleInterface *iface) -{ - QWindow *window = iface->window(); - if (!window) { - QAccessibleInterface *acc = iface->parent(); - while (acc && acc->isValid() && !window) { - window = acc->window(); - QAccessibleInterface *par = acc->parent(); - acc = par; - } - } - return window; -} - -/*! - \internal - helper to wrap a QAccessibleInterface inside a IAccessible* -*/ -IAccessible *QWindowsAccessibility::wrap(QAccessibleInterface *acc) -{ - if (!acc) - return 0; - - // ### FIXME: maybe we should accept double insertions into the cache - 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; -} - -bool QWindowsAccessibility::handleAccessibleObjectFromWindowRequest(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult) -{ - if (static_cast<long>(lParam) == static_cast<long>(UiaRootObjectId)) { - /* For UI Automation */ - } else if (DWORD(lParam) == DWORD(OBJID_CLIENT)) { - // Start handling accessibility internally - QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true); - // Ignoring all requests while starting up - // ### Maybe QPA takes care of this??? - if (QCoreApplication::startingUp() || QCoreApplication::closingDown()) - return false; - - typedef LRESULT (WINAPI *PtrLresultFromObject)(REFIID, WPARAM, LPUNKNOWN); - static PtrLresultFromObject ptrLresultFromObject = 0; - static bool oleaccChecked = false; - - if (!oleaccChecked) { - oleaccChecked = true; - ptrLresultFromObject = reinterpret_cast<PtrLresultFromObject>(QSystemLibrary::resolve(QLatin1String("oleacc"), "LresultFromObject")); - } - - if (ptrLresultFromObject) { - QWindow *window = QWindowsContext::instance()->findWindow(hwnd); - if (window) { - QAccessibleInterface *acc = window->accessibleRoot(); - if (acc) { - if (IAccessible *iface = wrap(acc)) { - *lResult = ptrLresultFromObject(IID_IAccessible, wParam, iface); // ref == 2 - if (*lResult) { - iface->Release(); // the client will release the object again, and then it will destroy itself - } - return true; - } - } - } - } - } - return false; -} - -QT_END_NAMESPACE - -#endif //QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp b/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp deleted file mode 100644 index 25b1577772..0000000000 --- a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.cpp +++ /dev/null @@ -1,1225 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 <QtCore/QtConfig> -#ifndef QT_NO_ACCESSIBILITY - -#include "qwindowsmsaaaccessible.h" -#include "qwindowsaccessibility.h" -#include <oleacc.h> -#include <servprov.h> -#include <winuser.h> -#include "comutils.h" - -#include <QtCore/qdebug.h> -#include <QtCore/qmap.h> -#include <QtCore/qpair.h> -#include <QtGui/qaccessible.h> -#include <QtGui/qguiapplication.h> -#include <qpa/qplatformnativeinterface.h> -#include <QtGui/qwindow.h> -#include <QtGui/private/qhighdpiscaling_p.h> - -//#include <uiautomationcoreapi.h> -#ifndef UiaRootObjectId -#define UiaRootObjectId -25 -#endif - -#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) -#include <comdef.h> -#endif - - -#include <QtCore/qt_windows.h> - - -QT_BEGIN_NAMESPACE - -class QWindowsEnumerate : public IEnumVARIANT -{ -public: - QWindowsEnumerate(const QVector<int> &a) - : ref(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; - *ppEnum = 0; - - penum = new QWindowsEnumerate(array); - if (!penum) - return E_OUTOFMEMORY; - penum->current = current; - penum->array = array; - penum->AddRef(); - *ppEnum = penum; - - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Next(unsigned long celt, VARIANT FAR* rgVar, unsigned long FAR* pCeltFetched) -{ - if (pCeltFetched) - *pCeltFetched = 0; - - ULONG l; - for (l = 0; l < celt; l++) { - VariantInit(&rgVar[l]); - if (current + 1 > ULONG(array.size())) { - *pCeltFetched = l; - return S_FALSE; - } - - rgVar[l].vt = VT_I4; - rgVar[l].lVal = array[int(current)]; - ++current; - } - *pCeltFetched = l; - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Reset() -{ - current = 0; - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Skip(unsigned long celt) -{ - current += celt; - if (current > ULONG(array.size())) { - current = ULONG(array.size()); - return S_FALSE; - } - return S_OK; -} - -#if defined(DEBUG_SHOW_ATCLIENT_COMMANDS) -void accessibleDebugClientCalls_helper(const char* funcName, const QAccessibleInterface *iface) -{ - qCDebug(lcQpaAccessibility) << iface << funcName; -} -#endif - -/**************************************************************\ - * * - * IUnknown * - * * - **************************************************************/ -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(); - } - 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; -} - -ULONG STDMETHODCALLTYPE QWindowsMsaaAccessible::AddRef() -{ - return ++ref; -} - -ULONG STDMETHODCALLTYPE QWindowsMsaaAccessible::Release() -{ - if (!--ref) { - delete this; - return 0; - } - return ref; -} - - -/* - IDispatch -*/ - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::GetTypeInfoCount(unsigned int * pctinfo) -{ - // We don't use a type library - *pctinfo = 0; - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::GetTypeInfo(unsigned int, unsigned long, ITypeInfo **pptinfo) -{ - // We don't use a type library - *pptinfo = 0; - return S_OK; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::GetIDsOfNames(const _GUID &, wchar_t **rgszNames, unsigned int, unsigned long, long *rgdispid) -{ -#if !defined(Q_CC_BOR) && !defined(Q_CC_GNU) - // PROPERTIES: Hierarchical - if (_bstr_t(rgszNames[0]) == _bstr_t(L"accParent")) - rgdispid[0] = DISPID_ACC_PARENT; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accChildCount")) - rgdispid[0] = DISPID_ACC_CHILDCOUNT; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accChild")) - rgdispid[0] = DISPID_ACC_CHILD; - - // PROPERTIES: Descriptional - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accName(")) - rgdispid[0] = DISPID_ACC_NAME; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accValue")) - rgdispid[0] = DISPID_ACC_VALUE; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accDescription")) - rgdispid[0] = DISPID_ACC_DESCRIPTION; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accRole")) - rgdispid[0] = DISPID_ACC_ROLE; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accState")) - rgdispid[0] = DISPID_ACC_STATE; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accHelp")) - rgdispid[0] = DISPID_ACC_HELP; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accHelpTopic")) - rgdispid[0] = DISPID_ACC_HELPTOPIC; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accKeyboardShortcut")) - rgdispid[0] = DISPID_ACC_KEYBOARDSHORTCUT; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accFocus")) - rgdispid[0] = DISPID_ACC_FOCUS; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accSelection")) - rgdispid[0] = DISPID_ACC_SELECTION; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accDefaultAction")) - rgdispid[0] = DISPID_ACC_DEFAULTACTION; - - // METHODS - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accSelect")) - rgdispid[0] = DISPID_ACC_SELECT; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accLocation")) - rgdispid[0] = DISPID_ACC_LOCATION; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accNavigate")) - rgdispid[0] = DISPID_ACC_NAVIGATE; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accHitTest")) - rgdispid[0] = DISPID_ACC_HITTEST; - else if (_bstr_t(rgszNames[0]) == _bstr_t(L"accDoDefaultAction")) - rgdispid[0] = DISPID_ACC_DODEFAULTACTION; - else - return DISP_E_UNKNOWNINTERFACE; - - return S_OK; -#else - Q_UNUSED(rgszNames); - Q_UNUSED(rgdispid); - - return DISP_E_MEMBERNOTFOUND; -#endif -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::Invoke(long dispIdMember, - const _GUID &, - unsigned long, - unsigned short wFlags, - tagDISPPARAMS *pDispParams, - tagVARIANT *pVarResult, - tagEXCEPINFO *, unsigned int *) -{ - HRESULT hr = DISP_E_MEMBERNOTFOUND; - - switch (dispIdMember) - { - case DISPID_ACC_PARENT: - if (wFlags == DISPATCH_PROPERTYGET) { - if (!pVarResult) - return E_INVALIDARG; - hr = get_accParent(&pVarResult->pdispVal); - } else { - hr = DISP_E_MEMBERNOTFOUND; - } - break; - - case DISPID_ACC_CHILDCOUNT: - if (wFlags == DISPATCH_PROPERTYGET) { - if (!pVarResult) - return E_INVALIDARG; - hr = get_accChildCount(&pVarResult->lVal); - } else { - hr = DISP_E_MEMBERNOTFOUND; - } - break; - - case DISPID_ACC_CHILD: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accChild(pDispParams->rgvarg[0], &pVarResult->pdispVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_NAME: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accName(pDispParams->rgvarg[0], &pVarResult->bstrVal); - else if (wFlags == DISPATCH_PROPERTYPUT) - hr = put_accName(pDispParams->rgvarg[0], pVarResult->bstrVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_VALUE: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accValue(pDispParams->rgvarg[0], &pVarResult->bstrVal); - else if (wFlags == DISPATCH_PROPERTYPUT) - hr = put_accValue(pDispParams->rgvarg[0], pVarResult->bstrVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_DESCRIPTION: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accDescription(pDispParams->rgvarg[0], &pVarResult->bstrVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_ROLE: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accRole(pDispParams->rgvarg[0], pVarResult); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_STATE: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accState(pDispParams->rgvarg[0], pVarResult); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_HELP: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accHelp(pDispParams->rgvarg[0], &pVarResult->bstrVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_HELPTOPIC: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accHelpTopic(&pDispParams->rgvarg[2].bstrVal, pDispParams->rgvarg[1], &pDispParams->rgvarg[0].lVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_KEYBOARDSHORTCUT: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accKeyboardShortcut(pDispParams->rgvarg[0], &pVarResult->bstrVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_FOCUS: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accFocus(pVarResult); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_SELECTION: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accSelection(pVarResult); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_DEFAULTACTION: - if (wFlags == DISPATCH_PROPERTYGET) - hr = get_accDefaultAction(pDispParams->rgvarg[0], &pVarResult->bstrVal); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_SELECT: - if (wFlags == DISPATCH_METHOD) - hr = accSelect(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0]); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_LOCATION: - if (wFlags == DISPATCH_METHOD) - hr = accLocation(&pDispParams->rgvarg[4].lVal, &pDispParams->rgvarg[3].lVal, &pDispParams->rgvarg[2].lVal, &pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0]); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_NAVIGATE: - if (wFlags == DISPATCH_METHOD) - hr = accNavigate(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0], pVarResult); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_HITTEST: - if (wFlags == DISPATCH_METHOD) - hr = accHitTest(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].lVal, pVarResult); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - case DISPID_ACC_DODEFAULTACTION: - if (wFlags == DISPATCH_METHOD) - hr = accDoDefaultAction(pDispParams->rgvarg[0]); - else - hr = DISP_E_MEMBERNOTFOUND; - break; - - default: - hr = DISP_E_MEMBERNOTFOUND; - break; - } - - if (!SUCCEEDED(hr)) { - return hr; - } - return hr; -} - -/* - IAccessible - -IAccessible::accHitTest documents the value returned in pvarID like this: - -| *Point location* | *vt member* | *Value member* | -+========================================================+=============+=========================+ -| Outside of the object's boundaries, and either inside | VT_EMPTY | None. | -| or outside of the object's bounding rectangle. | | | -+--------------------------------------------------------+-------------+-------------------------+ -| Within the object but not within a child element or a | VT_I4 | lVal is CHILDID_SELF | -| child object. | | | -+--------------------------------------------------------+-------------+-------------------------+ -| Within a child element. | VT_I4 | lVal contains | -| | | the child ID. | -+--------------------------------------------------------+-------------+-------------------------+ -| Within a child object. | VT_DISPATCH | pdispVal is set to the | -| | | child object's IDispatch| -| | | interface pointer | -+--------------------------------------------------------+-------------+-------------------------+ -*/ -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::accHitTest(long xLeft, long yTop, VARIANT *pvarID) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - const QPoint pos = QHighDpi::fromNativeLocalPosition(QPoint(xLeft, yTop), - QWindowsAccessibility::windowHelper(accessible)); - QAccessibleInterface *child = accessible->childAt(pos.x(), pos.y()); - if (child == 0) { - // no child found, return this item if it contains the coordinates - if (accessible->rect().contains(xLeft, yTop)) { - (*pvarID).vt = VT_I4; - (*pvarID).lVal = CHILDID_SELF; - return S_OK; - } - } else { - IAccessible *iface = QWindowsAccessibility::wrap(child); - if (iface) { - (*pvarID).vt = VT_DISPATCH; - (*pvarID).pdispVal = iface; - return S_OK; - } - } - - // Did not find anything - (*pvarID).vt = VT_EMPTY; - return S_FALSE; -} - -/* - It is recommended to read - "Implementing a Microsoft Active Accessibility (MSAA) Server. - Practical Tips for Developers and How Mozilla Does It" - (https://developer.mozilla.org/En/Accessibility/Implementing_an_MSAA_Server) - - to get an overview of what's important to implement and what parts of MSAA - can be ignored. All stuff prefixed with "moz" are information from that page. -*/ -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varID) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QAccessibleInterface *acc = childPointer(accessible, varID); - if (!acc || !acc->isValid()) - return E_FAIL; - const QRect rect = QHighDpi::toNativePixels(acc->rect(), - QWindowsAccessibility::windowHelper(accessible)); - - *pxLeft = rect.x(); - *pyTop = rect.y(); - *pcxWidth = rect.width(); - *pcyHeight = rect.height(); - - return S_OK; -} - -// moz: [important, but no need to implement up/down/left/right] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEnd) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QAccessibleInterface *acc = 0; - switch (navDir) { - case NAVDIR_FIRSTCHILD: - acc = accessible->child(0); - break; - case NAVDIR_LASTCHILD: - acc = accessible->child(accessible->childCount() - 1); - break; - case NAVDIR_NEXT: - case NAVDIR_PREVIOUS: - if (!varStart.lVal){ - QAccessibleInterface *parent = accessible->parent(); - if (parent && parent->isValid()) { - int index = parent->indexOfChild(accessible); - index += (navDir == NAVDIR_NEXT) ? 1 : -1; - if (index >= 0 && index < parent->childCount()) - acc = parent->child(index); - } - } else { - int index = varStart.lVal; - index += (navDir == NAVDIR_NEXT) ? 1 : -1; - if (index > 0 && index <= accessible->childCount()) - acc = accessible->child(index - 1); - } - break; - - // Geometrical - case NAVDIR_UP: - case NAVDIR_DOWN: - case NAVDIR_LEFT: - case NAVDIR_RIGHT: { - QAccessibleInterface *pIface = accessible->parent(); - if (pIface && pIface->isValid()) { - const int indexOfOurself = pIface->indexOfChild(accessible); - QRect startg = accessible->rect(); - QPoint startc = startg.center(); - QAccessibleInterface *candidate = 0; - unsigned mindist = UINT_MAX; // will work on screen sizes at least up to 46340x46340 - const int sibCount = pIface->childCount(); - for (int i = 0; i < sibCount; ++i) { - QAccessibleInterface *sibling = 0; - sibling = pIface->child(i); - Q_ASSERT(sibling); - if (i == indexOfOurself || sibling->state().invisible) { - //ignore ourself and invisible siblings - continue; - } - - QRect sibg = sibling->rect(); - QPoint sibc = sibg.center(); - QPoint sibp; - QPoint startp; - QPoint distp; - switch (navDir) { - case NAVDIR_LEFT: - startp = QPoint(startg.left(), startg.top() + startg.height() / 2); - sibp = QPoint(sibg.right(), sibg.top() + sibg.height() / 2); - if (QPoint(sibc - startc).x() >= 0) { - continue; - } - distp = sibp - startp; - break; - case NAVDIR_RIGHT: - startp = QPoint(startg.right(), startg.top() + startg.height() / 2); - sibp = QPoint(sibg.left(), sibg.top() + sibg.height() / 2); - if (QPoint(sibc - startc).x() <= 0) { - continue; - } - distp = sibp - startp; - break; - case NAVDIR_UP: - startp = QPoint(startg.left() + startg.width() / 2, startg.top()); - sibp = QPoint(sibg.left() + sibg.width() / 2, sibg.bottom()); - if (QPoint(sibc - startc).y() >= 0) { - continue; - } - distp = sibp - startp; - break; - case NAVDIR_DOWN: - startp = QPoint(startg.left() + startg.width() / 2, startg.bottom()); - sibp = QPoint(sibg.left() + sibg.width() / 2, sibg.top()); - if (QPoint(sibc - startc).y() <= 0) { - continue; - } - distp = sibp - startp; - break; - default: - break; - } - - // Since we're *comparing* (and not measuring) distances, we can compare the - // squared distance, (thus, no need to take the sqrt()). - unsigned dist = distp.x() * distp.x() + distp.y() * distp.y(); - if (dist < mindist) { - candidate = sibling; - mindist = dist; - } - } - acc = candidate; - } - } - break; - default: - break; - } - if (!acc) { - (*pvarEnd).vt = VT_EMPTY; - return S_FALSE; - } - - if (IAccessible *iface = QWindowsAccessibility::wrap(acc)) { - (*pvarEnd).vt = VT_DISPATCH; - (*pvarEnd).pdispVal = iface; - return S_OK; - } - - (*pvarEnd).vt = VT_EMPTY; - return S_FALSE; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accChild(VARIANT varChildID, IDispatch** ppdispChild) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (varChildID.vt != VT_I4) - return E_INVALIDARG; - - QAccessibleInterface *acc = childPointer(accessible, varChildID); - if (acc && acc->isValid()) { - *ppdispChild = QWindowsAccessibility::wrap(acc); - return S_OK; - } - - return E_FAIL; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accChildCount(long* pcountChildren) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *pcountChildren = accessible->childCount(); - return S_OK; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accParent(IDispatch** ppdispParent) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QAccessibleInterface *acc = accessible->parent(); - if (acc) { - if (IAccessible *iface = QWindowsAccessibility::wrap(acc)) { - *ppdispParent = iface; - return S_OK; - } - } - - *ppdispParent = 0; - return S_FALSE; -} - -/* - Properties and methods -*/ -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::accDoDefaultAction(VARIANT varID) -{ - Q_UNUSED(varID); - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleActionInterface *actionIface = accessible->actionInterface()) { - const QString def = actionIface->actionNames().value(0); - if (!def.isEmpty()) { - actionIface->doAction(def); - return S_OK; - } - } - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accDefaultAction(VARIANT varID, BSTR* pszDefaultAction) -{ - Q_UNUSED(varID); - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *pszDefaultAction = 0; - if (QAccessibleActionInterface *actionIface = accessible->actionInterface()) { - const QString def = actionIface->actionNames().value(0); - if (!def.isEmpty()) - *pszDefaultAction = QStringToBSTR(def); - } - return *pszDefaultAction ? S_OK : S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accDescription(VARIANT varID, BSTR* pszDescription) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - - QString descr; - if (varID.lVal) { - QAccessibleInterface *child = childPointer(accessible, varID); - if (!child || !child->isValid()) - return E_FAIL; - descr = child->text(QAccessible::Description); - } else { - descr = accessible->text(QAccessible::Description); - } - if (descr.size()) { - *pszDescription = QStringToBSTR(descr); - return S_OK; - } - - *pszDescription = 0; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accHelp(VARIANT varID, BSTR *pszHelp) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QString help; - if (varID.lVal) { - QAccessibleInterface *child = childPointer(accessible, varID); - if (!child || !child->isValid()) - return E_FAIL; - help = child->text(QAccessible::Help); - } else { - help = accessible->text(QAccessible::Help); - } - if (help.size()) { - *pszHelp = QStringToBSTR(help); - return S_OK; - } - - *pszHelp = 0; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accHelpTopic(BSTR *, VARIANT, long *) -{ - return DISP_E_MEMBERNOTFOUND; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accKeyboardShortcut(VARIANT varID, BSTR *pszKeyboardShortcut) -{ - Q_UNUSED(varID); - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - *pszKeyboardShortcut = 0; - if (QAccessibleActionInterface *actionIface = accessible->actionInterface()) { - const QString def = actionIface->actionNames().value(0); - if (!def.isEmpty()) { - const QString keyBoardShortCut = actionIface->keyBindingsForAction(def).value(0); - if (!keyBoardShortCut.isEmpty()) - *pszKeyboardShortcut = QStringToBSTR(keyBoardShortCut); - } - } - return *pszKeyboardShortcut ? S_OK : S_FALSE; -} - -static QAccessibleInterface *relatedInterface(QAccessibleInterface *iface, QAccessible::RelationFlag flag) -{ - typedef QPair<QAccessibleInterface *, QAccessible::Relation> RelationPair; - QVector<RelationPair> rels = iface->relations(flag); - - return rels.value(0).first; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accName(VARIANT varID, BSTR* pszName) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QString name; - if (varID.lVal) { - QAccessibleInterface *child = childPointer(accessible, varID); - if (!child || !child->isValid()) - return E_FAIL; - name = child->text(QAccessible::Name); - if (name.isEmpty()) { - if (QAccessibleInterface *labelInterface = relatedInterface(child, QAccessible::Label)) { - name = labelInterface->text(QAccessible::Name); - } - } - } else { - name = accessible->text(QAccessible::Name); - if (name.isEmpty()) { - if (QAccessibleInterface *labelInterface = relatedInterface(accessible, QAccessible::Label)) { - name = labelInterface->text(QAccessible::Name); - } - } - } - - QString shortcut = accessible->text(QAccessible::Accelerator); - if (!shortcut.isEmpty()) - name += QLatin1Char(' ') + shortcut; - - if (name.size()) { - *pszName = QStringToBSTR(name); - return S_OK; - } - - *pszName = 0; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::put_accName(VARIANT, BSTR) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - return DISP_E_MEMBERNOTFOUND; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accRole(VARIANT varID, VARIANT *pvarRole) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QAccessible::Role role; - if (varID.lVal) { - QAccessibleInterface *child = childPointer(accessible, varID); - if (!child || !child->isValid()) - return E_FAIL; - role = child->role(); - } else { - role = accessible->role(); - } - - if (role != QAccessible::NoRole) { - if (role >= QAccessible::LayeredPane) { - // This block should hopefully only be entered if the AT client - // does not support IAccessible2, since it should prefer IA2::role() then. - if (role == QAccessible::LayeredPane) - role = QAccessible::Pane; - else if (role == QAccessible::WebDocument) - role = QAccessible::Document; - else - role = QAccessible::Client; - } - (*pvarRole).vt = VT_I4; - (*pvarRole).lVal = role; - } else { - (*pvarRole).vt = VT_EMPTY; - } - return S_OK; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accState(VARIANT varID, VARIANT *pvarState) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QAccessible::State state; - if (varID.lVal) { - QAccessibleInterface *child = childPointer(accessible, varID); - if (!child || !child->isValid()) - return E_FAIL; - state = child->state(); - } else { - state = accessible->state(); - } - - LONG st = 0; - if (state.animated) - st |= STATE_SYSTEM_ANIMATED; - if (state.busy) - st |= STATE_SYSTEM_BUSY; - if (state.checked) - st |= STATE_SYSTEM_CHECKED; - if (state.collapsed) - st |= STATE_SYSTEM_COLLAPSED; - if (state.defaultButton) - st |= STATE_SYSTEM_DEFAULT; - if (state.expanded) - st |= STATE_SYSTEM_EXPANDED; - if (state.extSelectable) - st |= STATE_SYSTEM_EXTSELECTABLE; - if (state.focusable) - st |= STATE_SYSTEM_FOCUSABLE; - if (state.focused) - st |= STATE_SYSTEM_FOCUSED; - if (state.hasPopup) - st |= STATE_SYSTEM_HASPOPUP; - if (state.hotTracked) - st |= STATE_SYSTEM_HOTTRACKED; - if (state.invisible) - st |= STATE_SYSTEM_INVISIBLE; - if (state.linked) - st |= STATE_SYSTEM_LINKED; - if (state.marqueed) - st |= STATE_SYSTEM_MARQUEED; - if (state.checkStateMixed) - st |= STATE_SYSTEM_MIXED; - if (state.movable) - st |= STATE_SYSTEM_MOVEABLE; - if (state.multiSelectable) - st |= STATE_SYSTEM_MULTISELECTABLE; - if (state.offscreen) - st |= STATE_SYSTEM_OFFSCREEN; - if (state.pressed) - st |= STATE_SYSTEM_PRESSED; - if (state.passwordEdit) - st |= STATE_SYSTEM_PROTECTED; - if (state.readOnly) - st |= STATE_SYSTEM_READONLY; - if (state.selectable) - st |= STATE_SYSTEM_SELECTABLE; - if (state.selected) - st |= STATE_SYSTEM_SELECTED; - if (state.selfVoicing) - st |= STATE_SYSTEM_SELFVOICING; - if (state.sizeable) - st |= STATE_SYSTEM_SIZEABLE; - if (state.traversed) - st |= STATE_SYSTEM_TRAVERSED; - if (state.disabled) - st |= STATE_SYSTEM_UNAVAILABLE; - - (*pvarState).vt = VT_I4; - (*pvarState).lVal = st; - return S_OK; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accValue(VARIANT varID, BSTR* pszValue) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (varID.vt != VT_I4) - return E_INVALIDARG; - - if (!accessible || !accessible->isValid() || varID.lVal) { - return E_FAIL; - } - - QString value; - if (accessible->valueInterface()) { - value = accessible->valueInterface()->currentValue().toString(); - } else { - value = accessible->text(QAccessible::Value); - } - if (!value.isNull()) { - *pszValue = QStringToBSTR(value); - return S_OK; - } - - *pszValue = 0; - qCDebug(lcQpaAccessibility) << "return S_FALSE"; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::put_accValue(VARIANT, BSTR value) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - - if (!accessible || !accessible->isValid()) { - return E_FAIL; - } - - QString qstrValue = QString::fromWCharArray(value); - - if (accessible->valueInterface()) { - accessible->valueInterface()->setCurrentValue(qstrValue); - } else { - accessible->setText(QAccessible::Value, qstrValue); - } - - return S_OK; -} - -// moz: [important] -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::accSelect(long flagsSelect, VARIANT varID) -{ - Q_UNUSED(flagsSelect); - Q_UNUSED(varID); - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - bool res = false; - -/* - ### Check for accessibleTableInterface() or accessibleTextInterface() - - ### and if there are no ia2 interfaces we should do nothing?? - if (flagsSelect & SELFLAG_TAKEFOCUS) - res = accessible()->doAction(SetFocus, varID.lVal, QVariantList()); - if (flagsSelect & SELFLAG_TAKESELECTION) { - accessible()->doAction(ClearSelection, 0, QVariantList()); - res = accessible()->doAction(AddToSelection, varID.lVal, QVariantList()); - } - if (flagsSelect & SELFLAG_EXTENDSELECTION) - res = accessible()->doAction(ExtendSelection, varID.lVal, QVariantList()); - if (flagsSelect & SELFLAG_ADDSELECTION) - res = accessible()->doAction(AddToSelection, varID.lVal, QVariantList()); - if (flagsSelect & SELFLAG_REMOVESELECTION) - res = accessible()->doAction(RemoveSelection, varID.lVal, QVariantList()); -*/ - return res ? S_OK : S_FALSE; -} - -/*! - \internal - Can return: - - +-------------+------------------------------------------------------------------------------+ - | VT_EMPTY | None. Neither this object nor any of its children has the keyboard focus. | - +-------------+------------------------------------------------------------------------------+ - | VT_I4 | lVal is CHILDID_SELF. The object itself has the keyboard focus. | - +-------------+------------------------------------------------------------------------------+ - | VT_I4 | lVal contains the child ID of the child element that has the keyboard focus. | - +-------------+------------------------------------------------------------------------------+ - | VT_DISPATCH | pdispVal member is the address of the IDispatch interface for the child | - | | object that has the keyboard focus. | - +-------------+------------------------------------------------------------------------------+ - moz: [important] -*/ -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accFocus(VARIANT *pvarID) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - if (QAccessibleInterface *acc = accessible->focusChild()) { - if (acc == accessible) { - (*pvarID).vt = VT_I4; - (*pvarID).lVal = CHILDID_SELF; - return S_OK; - } else { - if (IAccessible *iface = QWindowsAccessibility::wrap(acc)) { - (*pvarID).vt = VT_DISPATCH; - (*pvarID).pdispVal = iface; - return S_OK; - } - } - } - (*pvarID).vt = VT_EMPTY; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::get_accSelection(VARIANT *pvarChildren) -{ - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - int cc = accessible->childCount(); - QVector<int> sel(cc); - int selIndex = 0; - for (int i = 0; i < cc; ++i) { - bool isSelected = false; - QAccessibleInterface *child = accessible->child(i); - if (child) { - isSelected = child->state().selected; - } - if (isSelected) - sel[selIndex++] = i+1; - } - sel.resize(selIndex); - if (sel.isEmpty()) { - (*pvarChildren).vt = VT_EMPTY; - return S_FALSE; - } - if (sel.size() == 1) { - (*pvarChildren).vt = VT_I4; - (*pvarChildren).lVal = sel[0]; - return S_OK; - } - IEnumVARIANT *iface = new QWindowsEnumerate(sel); - IUnknown *uiface; - iface->QueryInterface(IID_IUnknown, (void**)&uiface); - (*pvarChildren).vt = VT_UNKNOWN; - (*pvarChildren).punkVal = uiface; - - return S_OK; -} - -/**************************************************************\ - * IOleWindow * - **************************************************************/ -HRESULT STDMETHODCALLTYPE QWindowsMsaaAccessible::GetWindow(HWND *phwnd) -{ - *phwnd = 0; - QAccessibleInterface *accessible = accessibleInterface(); - accessibleDebugClientCalls(accessible); - if (!accessible) - return E_FAIL; - - QWindow *window = QWindowsAccessibility::windowHelper(accessible); - if (!window) - return E_FAIL; - - QPlatformNativeInterface *platform = QGuiApplication::platformNativeInterface(); - Q_ASSERT(platform); - *phwnd = (HWND)platform->nativeResourceForWindow("handle", window); - qCDebug(lcQpaAccessibility) << "QWindowsAccessible::GetWindow(): " << *phwnd; - return S_OK; -} - -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) -{ - 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); - - return QByteArray(); -} - -QT_END_NAMESPACE - -#endif //QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h b/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h deleted file mode 100644 index fd00f8ac8b..0000000000 --- a/src/plugins/platforms/windows/accessible/qwindowsmsaaaccessible.h +++ /dev/null @@ -1,175 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 QWINDOWSMSAAACCESSIBLE_H -#define QWINDOWSMSAAACCESSIBLE_H - -#include <QtCore/QtConfig> -#ifndef QT_NO_ACCESSIBILITY -#include <QtCore/qglobal.h> - -#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 - -QT_BEGIN_NAMESPACE - -#ifndef QT_NO_DEBUG_OUTPUT -#define DEBUG_SHOW_ATCLIENT_COMMANDS -#endif -#if defined(DEBUG_SHOW_ATCLIENT_COMMANDS) -void accessibleDebugClientCalls_helper(const char* funcName, const QAccessibleInterface *iface); -# define accessibleDebugClientCalls(iface) accessibleDebugClientCalls_helper(Q_FUNC_INFO, iface) -#else -# define accessibleDebugClientCalls(iface) -#endif - -QWindow *window_helper(const QAccessibleInterface *iface); - -/**************************************************************\ - * QWindowsAccessible * - **************************************************************/ -class QWindowsMsaaAccessible : public -#ifdef Q_CC_MINGW - IAccessible -#else - IAccessible2 -#endif - , public IOleWindow -{ -public: - QWindowsMsaaAccessible(QAccessibleInterface *a) - : ref(0) - { - id = QAccessible::uniqueId(a); - } - - virtual ~QWindowsMsaaAccessible() - { - } - - /* IUnknown */ - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - - /* IDispatch */ - HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int *); - HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int, unsigned long, ITypeInfo **); - HRESULT STDMETHODCALLTYPE GetIDsOfNames(const _GUID &, wchar_t **, unsigned int, unsigned long, long *); - HRESULT STDMETHODCALLTYPE Invoke(long, const _GUID &, unsigned long, unsigned short, tagDISPPARAMS *, tagVARIANT *, tagEXCEPINFO *, unsigned int *); - - /* IAccessible */ - HRESULT STDMETHODCALLTYPE accHitTest(long xLeft, long yTop, VARIANT *pvarID); - HRESULT STDMETHODCALLTYPE accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varID); - HRESULT STDMETHODCALLTYPE accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEnd); - HRESULT STDMETHODCALLTYPE get_accChild(VARIANT varChildID, IDispatch** ppdispChild); - HRESULT STDMETHODCALLTYPE get_accChildCount(long* pcountChildren); - HRESULT STDMETHODCALLTYPE get_accParent(IDispatch** ppdispParent); - - HRESULT STDMETHODCALLTYPE accDoDefaultAction(VARIANT varID); - HRESULT STDMETHODCALLTYPE get_accDefaultAction(VARIANT varID, BSTR* pszDefaultAction); - HRESULT STDMETHODCALLTYPE get_accDescription(VARIANT varID, BSTR* pszDescription); - HRESULT STDMETHODCALLTYPE get_accHelp(VARIANT varID, BSTR *pszHelp); - HRESULT STDMETHODCALLTYPE get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic); - HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut(VARIANT varID, BSTR *pszKeyboardShortcut); - HRESULT STDMETHODCALLTYPE get_accName(VARIANT varID, BSTR* pszName); - HRESULT STDMETHODCALLTYPE put_accName(VARIANT varChild, BSTR szName); - HRESULT STDMETHODCALLTYPE get_accRole(VARIANT varID, VARIANT *pvarRole); - HRESULT STDMETHODCALLTYPE get_accState(VARIANT varID, VARIANT *pvarState); - HRESULT STDMETHODCALLTYPE get_accValue(VARIANT varID, BSTR* pszValue); - HRESULT STDMETHODCALLTYPE put_accValue(VARIANT varChild, BSTR szValue); - - HRESULT STDMETHODCALLTYPE accSelect(long flagsSelect, VARIANT varID); - HRESULT STDMETHODCALLTYPE get_accFocus(VARIANT *pvarID); - HRESULT STDMETHODCALLTYPE get_accSelection(VARIANT *pvarChildren); - - /* IOleWindow */ - HRESULT STDMETHODCALLTYPE GetWindow(HWND *phwnd); - HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode); - -protected: - virtual QByteArray IIDToString(REFIID id); - - QAccessible::Id id; - - QAccessibleInterface *accessibleInterface() const - { - QAccessibleInterface *iface = QAccessible::accessibleInterface(id); - if (iface && iface->isValid()) - return iface; - return 0; - } - - static QAccessibleInterface *childPointer(QAccessibleInterface *parent, VARIANT varID) - { - // -1 since windows API always uses 1 for the first child - Q_ASSERT(parent); - - QAccessibleInterface *acc = 0; - int childIndex = varID.lVal; - if (childIndex == 0) { - // Yes, some AT clients (Active Accessibility Object Inspector) - // actually ask for the same object. As a consequence, we need to clone ourselves: - acc = parent; - } else if (childIndex < 0) { - acc = QAccessible::accessibleInterface((QAccessible::Id)childIndex); - } else { - acc = parent->child(childIndex - 1); - } - return acc; - } - -private: - ULONG ref; - -}; - -QT_END_NAMESPACE - -#endif //QT_NO_ACCESSIBILITY - -#endif // QWINDOWSMSAAACCESSIBLE_H 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/qwin10helpers.cpp b/src/plugins/platforms/windows/qwin10helpers.cpp index 12cccd124b..ac6a34d7c2 100644 --- a/src/plugins/platforms/windows/qwin10helpers.cpp +++ b/src/plugins/platforms/windows/qwin10helpers.cpp @@ -40,6 +40,7 @@ #include "qwin10helpers.h" #include <QtCore/QDebug> +#include <QtCore/QOperatingSystemVersion> #include <QtCore/private/qsystemlibrary_p.h> #if defined(Q_CC_MINGW) @@ -115,7 +116,7 @@ static QWindowsComBaseDLL baseComDll; bool QWindowsComBaseDLL::init() { - if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10 && !isValid()) { + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10 && !isValid()) { QSystemLibrary library(QStringLiteral("combase")); roGetActivationFactory = reinterpret_cast<RoGetActivationFactory>(library.resolve("RoGetActivationFactory")); diff --git a/src/plugins/platforms/windows/qwindowsbackingstore.cpp b/src/plugins/platforms/windows/qwindowsbackingstore.cpp index 49c7144221..80872c3ea3 100644 --- a/src/plugins/platforms/windows/qwindowsbackingstore.cpp +++ b/src/plugins/platforms/windows/qwindowsbackingstore.cpp @@ -93,7 +93,7 @@ void QWindowsBackingStore::flush(QWindow *window, const QRegion ®ion, // Windows with alpha: Use blend function to update. QRect r = QHighDpi::toNativePixels(window->frameGeometry(), window); QPoint frameOffset(QHighDpi::toNativePixels(QPoint(window->frameMargins().left(), window->frameMargins().top()), - static_cast<const QWindow *>(Q_NULLPTR))); + static_cast<const QWindow *>(nullptr))); QRect dirtyRect = br.translated(offset + frameOffset); SIZE size = {r.width(), r.height()}; 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..c146f8ec25 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) @@ -52,7 +53,7 @@ #include "qwindowstheme.h" #include <private/qguiapplication_p.h> #ifndef QT_NO_ACCESSIBILITY -# include "accessible/qwindowsaccessibility.h" +# include "uiautomation/qwindowsuiaaccessibility.h" #endif #if QT_CONFIG(sessionmanager) # include <private/qsessionmanager_p.h> @@ -94,8 +95,11 @@ 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(lcQpaUiAutomation, "qt.qpa.uiautomation") +Q_LOGGING_CATEGORY(lcQpaTrayIcon, "qt.qpa.trayicon") int QWindowsContext::verbose = 0; @@ -126,11 +130,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) @@ -182,26 +194,14 @@ void QWindowsUser32DLL::init() getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences"); setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences"); - if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10) { // Appears in 10.0.14393, October 2016 + if (QOperatingSystemVersion::current() + >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)) { enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling"); getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext"); getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext"); } } -bool QWindowsUser32DLL::initTouch() -{ - if (!isTouchWindow && QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) { - QSystemLibrary library(QStringLiteral("user32")); - isTouchWindow = (IsTouchWindow)(library.resolve("IsTouchWindow")); - registerTouchWindow = (RegisterTouchWindow)(library.resolve("RegisterTouchWindow")); - unregisterTouchWindow = (UnregisterTouchWindow)(library.resolve("UnregisterTouchWindow")); - getTouchInputInfo = (GetTouchInputInfo)(library.resolve("GetTouchInputInfo")); - closeTouchInputHandle = (CloseTouchInputHandle)(library.resolve("CloseTouchInputHandle")); - } - return isTouchWindow && registerTouchWindow && unregisterTouchWindow && getTouchInputInfo && closeTouchInputHandle; -} - void QWindowsShcoreDLL::init() { if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8_1) @@ -258,7 +258,7 @@ QWindowsContextPrivate::QWindowsContextPrivate() QWindowsContext::user32dll.init(); QWindowsContext::shcoredll.init(); - if (m_mouseHandler.touchDevice() && QWindowsContext::user32dll.initTouch()) + if (m_mouseHandler.touchDevice()) m_systemInfo |= QWindowsContext::SI_SupportsTouch; m_displayContext = GetDC(0); m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY); @@ -316,11 +316,6 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) if (!touchDevice) return false; - if (!QWindowsContext::user32dll.initTouch()) { - delete touchDevice; - return false; - } - if (!(integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) touchDevice->setCapabilities(touchDevice->capabilities() | QTouchDevice::MouseEmulation); @@ -397,9 +392,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 @@ -441,7 +438,7 @@ QString QWindowsContext::registerWindowClass(const QWindow *w) // QOpenGLWidget or QQuickWidget later on. That cannot be detected at this stage. if (w->surfaceType() == QSurface::OpenGLSurface || (flags & Qt::MSWindowsOwnDC)) style |= CS_OWNDC; - if (!(flags & Qt::NoDropShadowWindowHint) && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based) + if (!(flags & Qt::NoDropShadowWindowHint) && (type == Qt::Popup || w->property("_q_windowsDropShadow").toBool())) { style |= CS_DROPSHADOW; } @@ -597,6 +594,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); @@ -735,7 +741,7 @@ HWND QWindowsContext::createDummyWindow(const QString &classNameIn, // present in the MSVCRT.DLL found on Windows XP (QTBUG-35617). static inline QString errorMessageFromComError(const _com_error &comError) { - TCHAR *message = Q_NULLPTR; + TCHAR *message = nullptr; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, DWORD(comError.Error()), MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), message, 0, NULL); @@ -850,7 +856,7 @@ static inline bool resizeOnDpiChanged(const QWindow *w) static bool shouldHaveNonClientDpiScaling(const QWindow *window) { - return QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10 + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10 && window->isTopLevel() && !window->property(QWindowsWindow::embeddedNativeParentHandleProperty).isValid() #if QT_CONFIG(opengl) // /QTBUG-62901, EnableNonClientDpiScaling has problems with GL @@ -934,11 +940,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. @@ -956,7 +958,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, return false; case QtWindows::AccessibleObjectFromWindowRequest: #ifndef QT_NO_ACCESSIBILITY - return QWindowsAccessibility::handleAccessibleObjectFromWindowRequest(hwnd, wParam, lParam, result); + return QWindowsUiaAccessibility::handleWmGetObject(hwnd, wParam, lParam, result); #else return false; #endif @@ -1027,11 +1029,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 +1065,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 +1075,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 +1085,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 +1290,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..f2ec307be2 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -63,11 +63,15 @@ 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(lcQpaUiAutomation) +Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon) class QWindow; class QPlatformScreen; +class QWindowsMenuBar; class QWindowsScreenManager; class QWindowsTabletSupport; class QWindowsWindow; @@ -81,13 +85,7 @@ class QTouchDevice; struct QWindowsUser32DLL { inline void init(); - inline bool initTouch(); - typedef BOOL (WINAPI *IsTouchWindow)(HWND, PULONG); // Windows 7 - typedef BOOL (WINAPI *RegisterTouchWindow)(HWND, ULONG); - typedef BOOL (WINAPI *UnregisterTouchWindow)(HWND); - typedef BOOL (WINAPI *GetTouchInputInfo)(HANDLE, UINT, PVOID, int); - typedef BOOL (WINAPI *CloseTouchInputHandle)(HANDLE); typedef BOOL (WINAPI *SetProcessDPIAware)(); typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); @@ -97,13 +95,6 @@ struct QWindowsUser32DLL typedef int (WINAPI *GetWindowDpiAwarenessContext)(HWND); typedef int (WINAPI *GetAwarenessFromDpiAwarenessContext)(int); - // Touch functions from Windows 7 onwards (also for use with Q_CC_MSVC). - IsTouchWindow isTouchWindow = nullptr; - RegisterTouchWindow registerTouchWindow = nullptr; - UnregisterTouchWindow unregisterTouchWindow = nullptr; - GetTouchInputInfo getTouchInputInfo = nullptr; - CloseTouchInputHandle closeTouchInputHandle = nullptr; - // Windows Vista onwards SetProcessDPIAware setProcessDPIAware = nullptr; @@ -177,6 +168,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 +184,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 +208,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..e1a5837201 100644 --- a/src/plugins/platforms/windows/qwindowscursor.cpp +++ b/src/plugins/platforms/windows/qwindowscursor.cpp @@ -184,7 +184,7 @@ static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor = 1) return createBitmapCursor(bbits, mbits, cursor.hotSpot(), invb, invm); } -static QSize systemCursorSize(const QPlatformScreen *screen = Q_NULLPTR) +static QSize systemCursorSize(const QPlatformScreen *screen = nullptr) { const QSize primaryScreenCursorSize(GetSystemMetrics(SM_CXCURSOR), GetSystemMetrics(SM_CYCURSOR)); if (screen) { @@ -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..4772f3fce5 100644 --- a/src/plugins/platforms/windows/qwindowscursor.h +++ b/src/plugins/platforms/windows/qwindowscursor.h @@ -70,7 +70,7 @@ class CursorHandle { Q_DISABLE_COPY(CursorHandle) public: - explicit CursorHandle(HCURSOR hcursor = Q_NULLPTR) : m_hcursor(hcursor) {} + explicit CursorHandle(HCURSOR hcursor = nullptr) : m_hcursor(hcursor) {} ~CursorHandle() { if (m_hcursor) @@ -105,14 +105,17 @@ 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; static HCURSOR createPixmapCursor(QPixmap pixmap, const QPoint &hotSpot, qreal scaleFactor = 1); static HCURSOR createPixmapCursor(const PixmapCursor &pc, qreal scaleFactor = 1) { return createPixmapCursor(pc.pixmap, pc.hotSpot, scaleFactor); } - static PixmapCursor customCursor(Qt::CursorShape cursorShape, const QPlatformScreen *screen = Q_NULLPTR); + static PixmapCursor customCursor(Qt::CursorShape cursorShape, const QPlatformScreen *screen = nullptr); - static HCURSOR createCursorFromShape(Qt::CursorShape cursorShape, const QPlatformScreen *screen = Q_NULLPTR); + static HCURSOR createCursorFromShape(Qt::CursorShape cursorShape, const QPlatformScreen *screen = nullptr); static QPoint mousePosition(); static CursorState cursorState(); @@ -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 bdae764025..80ee7b2287 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> @@ -503,33 +506,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; } @@ -545,7 +526,6 @@ public: virtual ~QWindowsNativeFileDialogEventHandler() {} private: - long m_ref = 1; QWindowsNativeFileDialogBase *m_nativeFileDialog; }; @@ -620,8 +600,8 @@ QString QWindowsShellItem::path() const { if (isFileSystem()) return QDir::cleanPath(QWindowsShellItem::displayName(m_item, SIGDN_FILESYSPATH)); - // Check for a "Library" item (Windows 7) - if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7 && isDir()) + // Check for a "Library" item + if (isDir()) return QWindowsShellItem::libraryItemDefaultSaveFolder(m_item); return QString(); } @@ -732,7 +712,7 @@ QString QWindowsShellItem::libraryItemDefaultSaveFolder(IShellItem *item) { QString result; if (IShellLibrary *library = sHLoadLibraryFromItem(item, STGM_READ | STGM_SHARE_DENY_WRITE)) { - IShellItem *item = Q_NULLPTR; + IShellItem *item = nullptr; if (SUCCEEDED(library->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, reinterpret_cast<void **>(&item)))) { result = QDir::cleanPath(QWindowsShellItem::displayName(item, SIGDN_FILESYSPATH)); item->Release(); @@ -914,7 +894,7 @@ void QWindowsNativeFileDialogBase::setWindowTitle(const QString &title) IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) { if (url.isLocalFile()) { - IShellItem *result = Q_NULLPTR; + IShellItem *result = nullptr; const QString native = QDir::toNativeSeparators(url.toLocalFile()); const HRESULT hr = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()), @@ -922,30 +902,30 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) reinterpret_cast<void **>(&result)); if (FAILED(hr)) { qErrnoWarning("%s: SHCreateItemFromParsingName(%s)) failed", __FUNCTION__, qPrintable(url.toString())); - return Q_NULLPTR; + return nullptr; } return result; } else if (url.scheme() == QLatin1String("clsid")) { // Support for virtual folders via GUID // (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()); + IShellItem *result = nullptr; + const auto uuid = QUuid::fromString(url.path()); if (uuid.isNull()) { qWarning() << __FUNCTION__ << ": Invalid CLSID: " << url.path(); - return Q_NULLPTR; + return nullptr; } PIDLIST_ABSOLUTE idList; HRESULT hr = SHGetKnownFolderIDList(uuid, 0, 0, &idList); if (FAILED(hr)) { qErrnoWarning("%s: SHGetKnownFolderIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString())); - return Q_NULLPTR; + return nullptr; } hr = SHCreateItemFromIDList(idList, IID_IShellItem, reinterpret_cast<void **>(&result)); CoTaskMemFree(idList); if (FAILED(hr)) { qErrnoWarning("%s: SHCreateItemFromIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString())); - return Q_NULLPTR; + return nullptr; } return result; } else { @@ -994,7 +974,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) { @@ -1040,7 +1022,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' @@ -2084,7 +2066,7 @@ bool useHelper(QPlatformTheme::DialogType type) return false; switch (type) { case QPlatformTheme::FileDialog: - return QSysInfo::windowsVersion() >= QSysInfo::WV_XP; + return true; case QPlatformTheme::ColorDialog: #ifdef USE_NATIVE_COLOR_DIALOG return true; @@ -2105,13 +2087,10 @@ QPlatformDialogHelper *createHelper(QPlatformTheme::DialogType type) if (QWindowsIntegration::instance()->options() & QWindowsIntegration::NoNativeDialogs) return 0; switch (type) { - case QPlatformTheme::FileDialog: // Note: "Windows XP Professional x64 Edition has version number WV_5_2 (WV_2003). - if (QWindowsIntegration::instance()->options() & QWindowsIntegration::XpNativeDialogs - || QSysInfo::windowsVersion() <= QSysInfo::WV_2003) { + case QPlatformTheme::FileDialog: + if (QWindowsIntegration::instance()->options() & QWindowsIntegration::XpNativeDialogs) return new QWindowsXpFileDialogHelper(); - } - if (QSysInfo::windowsVersion() > QSysInfo::WV_2003) - return new QWindowsFileDialogHelper(); + return new QWindowsFileDialogHelper; case QPlatformTheme::ColorDialog: #ifdef USE_NATIVE_COLOR_DIALOG return new QWindowsColorDialogHelper(); diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index 26403b68e5..777c45ae86 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); @@ -251,13 +246,12 @@ private: typedef QMap<Qt::DropAction, CursorEntry> ActionCursorMap; - const Mode m_mode; + Mode m_mode; QWindowsDrag *m_drag; Qt::MouseButtons m_currentButtons; 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; } @@ -308,6 +301,15 @@ void QWindowsOleDropSource::createCursors() Q_ASSERT(platformScreen); QPlatformCursor *platformCursor = platformScreen->cursor(); + if (GetSystemMetrics (SM_REMOTESESSION) != 0) { + /* Workaround for RDP issues with large cursors. + * Touch drag window seems to work just fine... + * 96 pixel is a 'large' mouse cursor, according to RDP spec */ + const int rdpLargeCursor = qRound(qreal(96) / QHighDpiScaling::factor(platformScreen)); + if (pixmap.width() > rdpLargeCursor || pixmap.height() > rdpLargeCursor) + m_mode = TouchDrag; + } + qreal pixmapScaleFactor = 1; qreal hotSpotScaleFactor = 1; if (m_mode != TouchDrag) { // Touch drag: pixmap is shown in a separate QWindow, which will be scaled.) @@ -373,38 +375,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. */ @@ -472,6 +442,9 @@ QWindowsOleDropSource::GiveFeedback(DWORD dwEffect) SetCursor(e.cursor->handle()); break; case TouchDrag: + // "Touch drag" with an unsuppressed cursor may happen with RDP (see createCursors()) + if (QWindowsCursor::cursorState() != QWindowsCursor::CursorSuppressed) + SetCursor(nullptr); if (!m_touchDragWindow) m_touchDragWindow = new QWindowsDragCursorWindow; m_touchDragWindow->setPixmap(e.pixmap); @@ -509,34 +482,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 +685,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/qwindowseglcontext.h b/src/plugins/platforms/windows/qwindowseglcontext.h index 47878a7169..3e5f2c81d5 100644 --- a/src/plugins/platforms/windows/qwindowseglcontext.h +++ b/src/plugins/platforms/windows/qwindowseglcontext.h @@ -95,7 +95,7 @@ struct QWindowsLibGLESv2 #if !defined(QT_STATIC) || defined(QT_OPENGL_DYNAMIC) void *moduleHandle() const { return m_lib; } #else - void *moduleHandle() const { return Q_NULLPTR; } + void *moduleHandle() const { return nullptr; } #endif const GLubyte * (APIENTRY * glGetString)(GLenum name); diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index 751807e897..4bdf3167e4 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; @@ -375,9 +379,7 @@ static PIXELFORMATDESCRIPTOR initPixelFormatDescriptor(&pfd); pfd.iPixelType = PFD_TYPE_RGBA; pfd.iLayerType = PFD_MAIN_PLANE; - pfd.dwFlags = PFD_SUPPORT_OPENGL; - if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) - pfd.dwFlags = PFD_SUPPORT_COMPOSITION; + pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_SUPPORT_COMPOSITION; const bool isPixmap = (additional.formatFlags & QWindowsGLRenderToPixmap) != 0; pfd.dwFlags |= isPixmap ? PFD_DRAW_TO_BITMAP : PFD_DRAW_TO_WINDOW; if (!(additional.formatFlags & QWindowsGLDirectRendering)) @@ -489,7 +491,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 +572,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 +588,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 +643,7 @@ static QSurfaceFormat HDC hdc, int pixelFormat, QWindowsOpenGLAdditionalFormat *additionalIn = 0) { - enum { attribSize =40 }; + enum { attribSize = 42 }; QSurfaceFormat result; result.setRenderableType(QSurfaceFormat::OpenGL); @@ -641,6 +656,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 +674,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 +692,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 +972,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 +1117,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/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp index 8c228f588e..b9dd2c557e 100644 --- a/src/plugins/platforms/windows/qwindowsinputcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp @@ -221,6 +221,28 @@ void QWindowsInputContext::setFocusObject(QObject *) updateEnabled(); } +HWND QWindowsInputContext::getVirtualKeyboardWindowHandle() const +{ + return ::FindWindowA("IPTip_Main_Window", nullptr); +} + +QRectF QWindowsInputContext::keyboardRect() const +{ + if (HWND hwnd = getVirtualKeyboardWindowHandle()) { + RECT rect; + if (::GetWindowRect(hwnd, &rect)) { + return QRectF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + } + return QRectF(); +} + +bool QWindowsInputContext::isInputPanelVisible() const +{ + HWND hwnd = getVirtualKeyboardWindowHandle(); + return hwnd && ::IsWindowEnabled(hwnd) && ::IsWindowVisible(hwnd); +} + void QWindowsInputContext::updateEnabled() { if (!QGuiApplication::focusObject()) diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.h b/src/plugins/platforms/windows/qwindowsinputcontext.h index 617ef30cef..ada1fc0d29 100644 --- a/src/plugins/platforms/windows/qwindowsinputcontext.h +++ b/src/plugins/platforms/windows/qwindowsinputcontext.h @@ -79,6 +79,9 @@ public: void invokeAction(QInputMethod::Action, int cursorPosition) override; void setFocusObject(QObject *object) override; + QRectF keyboardRect() const override; + bool isInputPanelVisible() const override; + bool startComposition(HWND hwnd); bool composition(HWND hwnd, LPARAM lParam); bool endComposition(HWND hwnd); @@ -98,6 +101,7 @@ private: void startContextComposition(); void endContextComposition(); void updateEnabled(); + HWND getVirtualKeyboardWindowHandle() const; const DWORD m_WM_MSIME_MOUSE; static HIMC m_defaultContext; diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 17cab69891..287b65cd5d 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" @@ -59,7 +60,7 @@ #include "qwindowsinputcontext.h" #include "qwindowskeymapper.h" #ifndef QT_NO_ACCESSIBILITY -# include "accessible/qwindowsaccessibility.h" +# include "uiautomation/qwindowsuiaaccessibility.h" #endif #include <qpa/qplatformnativeinterface.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> @@ -149,7 +151,7 @@ struct QWindowsIntegrationPrivate #endif // QT_NO_OPENGL QScopedPointer<QPlatformInputContext> m_inputContext; #ifndef QT_NO_ACCESSIBILITY - QWindowsAccessibility m_accessibility; + QWindowsUiaAccessibility m_accessibility; #endif QWindowsServices m_services; }; @@ -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() @@ -245,7 +252,7 @@ QWindowsIntegrationPrivate::~QWindowsIntegrationPrivate() delete m_fontDatabase; } -QWindowsIntegration *QWindowsIntegration::m_instance = Q_NULLPTR; +QWindowsIntegration *QWindowsIntegration::m_instance = nullptr; QWindowsIntegration::QWindowsIntegration(const QStringList ¶mList) : d(new QWindowsIntegrationPrivate(paramList)) @@ -259,7 +266,7 @@ QWindowsIntegration::QWindowsIntegration(const QStringList ¶mList) : QWindowsIntegration::~QWindowsIntegration() { - m_instance = Q_NULLPTR; + m_instance = nullptr; } void QWindowsIntegration::initialize() @@ -329,25 +336,13 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons << " handle=" << obtained.hwnd << ' ' << obtained.flags << '\n'; if (Q_UNLIKELY(!obtained.hwnd)) - return Q_NULLPTR; + return nullptr; 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; } @@ -361,7 +356,7 @@ QPlatformWindow *QWindowsIntegration::createForeignWindow(QWindow *window, WId n } QWindowsForeignWindow *result = new QWindowsForeignWindow(window, hwnd); const QRect obtainedGeometry = result->geometry(); - QScreen *screen = Q_NULLPTR; + QScreen *screen = nullptr; if (const QPlatformScreen *pScreen = result->screenForGeometry(obtainedGeometry)) screen = pScreen->screen(); if (screen && screen != window->screen()) @@ -407,7 +402,7 @@ QWindowsStaticOpenGLContext *QWindowsStaticOpenGLContext::doCreate() qCWarning(lcQpaGl, "Software OpenGL failed. Falling back to system OpenGL."); if (QWindowsOpenGLTester::supportedRenderers() & QWindowsOpenGLTester::DesktopGl) return QOpenGLStaticContext::create(); - return Q_NULLPTR; + return nullptr; default: break; } @@ -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..814291c54a 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp @@ -56,37 +56,6 @@ #include <windowsx.h> -/* Touch is supported from Windows 7 onwards and data structures - * are present in the Windows SDK's, but not in older MSVC Express - * versions. */ - -#if defined(Q_CC_MINGW) || !defined(TOUCHEVENTF_MOVE) - -typedef struct tagTOUCHINPUT { - LONG x; - LONG y; - HANDLE hSource; - DWORD dwID; - DWORD dwFlags; - DWORD dwMask; - DWORD dwTime; - ULONG_PTR dwExtraInfo; - DWORD cxContact; - DWORD cyContact; -} TOUCHINPUT, *PTOUCHINPUT; -typedef TOUCHINPUT const * PCTOUCHINPUT; - -# define TOUCHEVENTF_MOVE 0x0001 -# define TOUCHEVENTF_DOWN 0x0002 -# define TOUCHEVENTF_UP 0x0004 -# define TOUCHEVENTF_INRANGE 0x0008 -# define TOUCHEVENTF_PRIMARY 0x0010 -# define TOUCHEVENTF_NOCOALESCE 0x0020 -# define TOUCHEVENTF_PALM 0x0080 -# define TOUCHINPUTMASKF_CONTACTAREA 0x0004 -# define TOUCHINPUTMASKF_EXTRAINFO 0x0002 -#endif // if defined(Q_CC_MINGW) || !defined(TOUCHEVENTF_MOVE) - QT_BEGIN_NAMESPACE static inline void compressMouseMove(MSG *msg) @@ -154,8 +123,6 @@ static inline QTouchDevice *createTouchDevice() QT_NID_INTEGRATED_TOUCH = 0x1, QT_NID_EXTERNAL_TOUCH = 0x02, QT_NID_MULTI_INPUT = 0x40, QT_NID_READY = 0x80 }; - if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7) - return 0; const int digitizers = GetSystemMetrics(QT_SM_DIGITIZER); if (!(digitizers & (QT_NID_INTEGRATED_TOUCH | QT_NID_EXTERNAL_TOUCH))) return 0; @@ -523,9 +490,8 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, touchPoints.reserve(winTouchPointCount); Qt::TouchPointStates allStates = 0; - QWindowsContext::user32dll.getTouchInputInfo(reinterpret_cast<HANDLE>(msg.lParam), - UINT(msg.wParam), - winTouchInputs.data(), sizeof(TOUCHINPUT)); + GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(msg.lParam), + UINT(msg.wParam), winTouchInputs.data(), sizeof(TOUCHINPUT)); for (int i = 0; i < winTouchPointCount; ++i) { const TOUCHINPUT &winTouchInput = winTouchInputs[i]; int id = m_touchInputIDToTouchPointID.value(winTouchInput.dwID, -1); @@ -566,7 +532,7 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, touchPoints.append(touchPoint); } - QWindowsContext::user32dll.closeTouchInputHandle(reinterpret_cast<HANDLE>(msg.lParam)); + CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(msg.lParam)); // all touch points released, forget the ids we've seen, they may not be reused if (allStates == Qt::TouchPointReleased) @@ -574,7 +540,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..324b00144e 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; @@ -126,7 +134,7 @@ void *QWindowsNativeInterface::nativeResourceForCursor(const QByteArray &resourc return static_cast<const QWindowsCursor *>(pCursor)->hCursor(cursor); } } - return Q_NULLPTR; + return nullptr; } #endif // !QT_NO_CURSOR @@ -272,7 +280,7 @@ QFunctionPointer QWindowsNativeInterface::platformFunction(const QByteArray &fun return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenStatic); else if (function == QWindowsWindowFunctions::isTabletModeIdentifier()) return QFunctionPointer(QWindowsNativeInterface::isTabletMode); - return Q_NULLPTR; + return nullptr; } QVariant QWindowsNativeInterface::gpu() const 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/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index cfddb3cc71..c0781df973 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -563,7 +563,7 @@ const QWindowsScreen *QWindowsScreenManager::screenAtDp(const QPoint &p) const if (scr->geometry().contains(p)) return scr; } - return Q_NULLPTR; + return nullptr; } const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const 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 c1aeecf0ab..038ee5cd62 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -37,6 +37,13 @@ ** ****************************************************************************/ +#if defined(WINVER) && WINVER < 0x0601 +# undef WINVER +#endif +#if !defined(WINVER) +# define WINVER 0x0601 // Enable touch functions for MinGW +#endif + #include "qwindowswindow.h" #include "qwindowscontext.h" #if QT_CONFIG(draganddrop) @@ -44,6 +51,7 @@ #endif #include "qwindowsscreen.h" #include "qwindowsintegration.h" +#include "qwindowsmenu.h" #include "qwindowsnativeinterface.h" #if QT_CONFIG(dynamicgl) # include "qwindowsglcontext.h" @@ -70,8 +78,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 +241,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 +325,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 +394,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 +412,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 +631,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 +648,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 +703,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) { @@ -834,7 +865,7 @@ QWindowsBaseWindow *QWindowsBaseWindow::baseWindowOf(const QWindow *w) if (QPlatformWindow *pw = w->handle()) return static_cast<QWindowsBaseWindow *>(pw); } - return Q_NULLPTR; + return nullptr; } HWND QWindowsBaseWindow::handleOf(const QWindow *w) @@ -985,10 +1016,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 +1034,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 +1081,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 +1097,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); @@ -1078,11 +1120,32 @@ QWindowsWindow::~QWindowsWindow() { setFlag(WithinDestroy); if (testFlag(TouchRegistered)) - QWindowsContext::user32dll.unregisterTouchWindow(m_data.hwnd); + UnregisterTouchWindow(m_data.hwnd); destroyWindow(); 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 +1178,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 +1403,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 +1465,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 +1478,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 +1535,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 +1555,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 +1571,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 +1616,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 +1663,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 +1776,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 +1836,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 +1866,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 +1899,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 +1922,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 +1930,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 +1945,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 +1975,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; } @@ -1942,9 +2071,17 @@ void QWindowsWindow::propagateSizeHints() bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins) { + WINDOWPOS *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam); + if ((windowPos->flags & SWP_NOZORDER) == 0) { + if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(qWindow)) { + QWindow *parentWindow = qWindow->parent(); + HWND parentHWND = GetAncestor(windowPos->hwnd, GA_PARENT); + HWND desktopHWND = GetDesktopWindow(); + platformWindow->m_data.embedded = !parentWindow && parentHWND && (parentHWND != desktopHWND); + } + } if (!qWindow->isTopLevel()) // Implement hasHeightForWidth(). return false; - WINDOWPOS *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam); if ((windowPos->flags & (SWP_NOCOPYBITS | SWP_NOSIZE))) return false; const QRect suggestedFrameGeometry(windowPos->x, windowPos->y, @@ -1989,7 +2126,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); } } @@ -2140,6 +2277,16 @@ bool QWindowsWindow::startSystemResize(const QPoint &, Qt::Corner corner) return true; } +bool QWindowsWindow::startSystemMove(const QPoint &) +{ + if (!GetSystemMenu(m_data.hwnd, FALSE)) + return false; + + ReleaseCapture(); + PostMessage(m_data.hwnd, WM_SYSCOMMAND, 0xF012 /*SC_DRAGMOVE*/, 0); + return true; +} + void QWindowsWindow::setFrameStrutEventsEnabled(bool enabled) { if (enabled) { @@ -2158,7 +2305,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 +2335,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 +2526,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 +2564,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 +2596,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()) @@ -2444,12 +2625,12 @@ void QWindowsWindow::registerTouchWindow(QWindowsWindowFunctions::TouchWindowTou if ((QWindowsContext::instance()->systemInfo() & QWindowsContext::SI_SupportsTouch) && !testFlag(TouchRegistered)) { ULONG touchFlags = 0; - const bool ret = QWindowsContext::user32dll.isTouchWindow(m_data.hwnd, &touchFlags); + const bool ret = IsTouchWindow(m_data.hwnd, &touchFlags); // Return if it is not a touch window or the flags are already set by a hook // such as HCBT_CREATEWND if (ret || touchFlags != 0) return; - if (QWindowsContext::user32dll.registerTouchWindow(m_data.hwnd, ULONG(touchTypes))) + if (RegisterTouchWindow(m_data.hwnd, ULONG(touchTypes))) setFlag(TouchRegistered); else qErrnoWarning("RegisterTouchWindow() failed for window '%s'.", qPrintable(window()->objectName())); diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 2b447751ba..3732255738 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; @@ -257,6 +269,7 @@ public: inline bool hasMouseCapture() const { return GetCapture() == m_data.hwnd; } bool startSystemResize(const QPoint &pos, Qt::Corner corner) override; + bool startSystemMove(const QPoint &pos) override; void setFrameStrutEventsEnabled(bool enabled) override; bool frameStrutEventsEnabled() const override { return testFlag(FrameStrutEventsEnabled); } @@ -265,6 +278,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 +344,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 +352,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 +372,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 +386,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/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp new file mode 100644 index 0000000000..907883bf5b --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiaaccessibility.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" + +#include <QtGui/QAccessible> +#include <QtGui/QWindow> +#include <QtGui/QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QtCore/qt_windows.h> +#include <qpa/qplatformintegration.h> +#include <QtWindowsUIAutomationSupport/private/qwindowsuiawrapper_p.h> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaAccessibility::QWindowsUiaAccessibility() +{ +} + +QWindowsUiaAccessibility::~QWindowsUiaAccessibility() +{ +} + +// Handles UI Automation window messages. +bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult) +{ + if (lParam == LPARAM(UiaRootObjectId)) { + + // Start handling accessibility internally + QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true); + + // Ignoring all requests while starting up / shutting down + if (QCoreApplication::startingUp() || QCoreApplication::closingDown()) + return false; + + if (QWindow *window = QWindowsContext::instance()->findWindow(hwnd)) { + if (QAccessibleInterface *accessible = window->accessibleRoot()) { + QWindowsUiaMainProvider *provider = QWindowsUiaMainProvider::providerForAccessible(accessible); + *lResult = QWindowsUiaWrapper::instance()->returnRawElementProvider(hwnd, wParam, lParam, provider); + return true; + } + } + } + return false; +} + +// Handles accessibility update notifications. +void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) +{ + if (!event) + return; + + QAccessibleInterface *accessible = event->accessibleInterface(); + if (!isActive() || !accessible || !accessible->isValid()) + return; + + // Ensures QWindowsUiaWrapper is properly initialized. + if (!QWindowsUiaWrapper::instance()->ready()) + return; + + // No need to do anything when nobody is listening. + if (!QWindowsUiaWrapper::instance()->clientsAreListening()) + return; + + switch (event->type()) { + + case QAccessible::Focus: + QWindowsUiaMainProvider::notifyFocusChange(event); + break; + + case QAccessible::StateChanged: + QWindowsUiaMainProvider::notifyStateChange(static_cast<QAccessibleStateChangeEvent *>(event)); + break; + + case QAccessible::ValueChanged: + QWindowsUiaMainProvider::notifyValueChange(static_cast<QAccessibleValueChangeEvent *>(event)); + break; + + case QAccessible::TextAttributeChanged: + case QAccessible::TextColumnChanged: + case QAccessible::TextInserted: + case QAccessible::TextRemoved: + case QAccessible::TextUpdated: + case QAccessible::TextSelectionChanged: + case QAccessible::TextCaretMoved: + QWindowsUiaMainProvider::notifyTextChange(event); + break; + + default: + break; + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h index 8621e93120..bbb81d596b 100644 --- a/src/plugins/platforms/windows/accessible/qwindowsaccessibility.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h @@ -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. @@ -37,27 +37,29 @@ ** ****************************************************************************/ -#ifndef QWINDOWSACCESSIBILITY_H -#define QWINDOWSACCESSIBILITY_H +#ifndef QWINDOWSUIAACCESSIBILITY_H +#define QWINDOWSUIAACCESSIBILITY_H -#include "../qtwindowsglobal.h" -#include "../qwindowscontext.h" -#include <qpa/qplatformaccessibility.h> +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY -#include <oleacc.h> +#include "qwindowscontext.h" +#include <qpa/qplatformaccessibility.h> QT_BEGIN_NAMESPACE -class QWindowsAccessibility : public QPlatformAccessibility +// Windows plataform accessibility implemented over UI Automation. +class QWindowsUiaAccessibility : public QPlatformAccessibility { public: - QWindowsAccessibility(); - static bool handleAccessibleObjectFromWindowRequest(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult); + explicit QWindowsUiaAccessibility(); + virtual ~QWindowsUiaAccessibility(); + static bool handleWmGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult); void notifyAccessibilityUpdate(QAccessibleEvent *event) override; - static IAccessible *wrap(QAccessibleInterface *acc); - static QWindow *windowHelper(const QAccessibleInterface *iface); }; QT_END_NAMESPACE -#endif // QWINDOWSACCESSIBILITY_H +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAACCESSIBILITY_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.cpp new file mode 100644 index 0000000000..1e1fc49c0f --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaBaseProvider::QWindowsUiaBaseProvider(QAccessible::Id id) : + m_id(id) +{ +} + +QWindowsUiaBaseProvider::~QWindowsUiaBaseProvider() +{ +} + +QAccessibleInterface *QWindowsUiaBaseProvider::accessibleInterface() const +{ + QAccessibleInterface *accessible = QAccessible::accessibleInterface(m_id); + if (accessible && accessible->isValid()) + return accessible; + return nullptr; +} + +QAccessible::Id QWindowsUiaBaseProvider::id() const +{ + return m_id; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h new file mode 100644 index 0000000000..3ae403e8c5 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIABASEPROVIDER_H +#define QWINDOWSUIABASEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QPointer> + +#include <qwindowscombase.h> +#include <QtWindowsUIAutomationSupport/private/qwindowsuiawrapper_p.h> + +QT_BEGIN_NAMESPACE + +class QAccessibleInterface; +class QDebug; + +// Base class for UI Automation providers. +class QWindowsUiaBaseProvider : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QWindowsUiaBaseProvider) +public: + explicit QWindowsUiaBaseProvider(QAccessible::Id id); + virtual ~QWindowsUiaBaseProvider(); + + QAccessibleInterface *accessibleInterface() const; + QAccessible::Id id() const; + +private: + QAccessible::Id m_id; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIABASEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.cpp new file mode 100644 index 0000000000..e0502c00f3 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiagriditemprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaGridItemProvider::QWindowsUiaGridItemProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaGridItemProvider::~QWindowsUiaGridItemProvider() +{ +} + +// Returns the row index of the item. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridItemProvider::get_Row(int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = tableCellInterface->rowIndex(); + return S_OK; +} + +// Returns the column index of the item. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridItemProvider::get_Column(int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = tableCellInterface->columnIndex(); + return S_OK; +} + +// Returns the number of rows occupied by the item. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridItemProvider::get_RowSpan(int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = tableCellInterface->rowExtent(); + return S_OK; +} + +// Returns the number of columns occupied by the item. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridItemProvider::get_ColumnSpan(int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = tableCellInterface->columnExtent(); + return S_OK; +} + +// Returns the provider for the cointaining table/tree. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridItemProvider::get_ContainingGrid(IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (QAccessibleInterface *table = tableCellInterface->table()) { + *pRetVal = QWindowsUiaMainProvider::providerForAccessible(table); + } + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h new file mode 100644 index 0000000000..a93b50ef97 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAGRIDITEMPROVIDER_H +#define QWINDOWSUIAGRIDITEMPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Grid Item control pattern provider. Used by items within a table/tree. +class QWindowsUiaGridItemProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<IGridItemProvider> +{ + Q_DISABLE_COPY(QWindowsUiaGridItemProvider) +public: + explicit QWindowsUiaGridItemProvider(QAccessible::Id id); + virtual ~QWindowsUiaGridItemProvider(); + + // IGridItemProvider + HRESULT STDMETHODCALLTYPE get_Row(int *pRetVal); + HRESULT STDMETHODCALLTYPE get_Column(int *pRetVal); + HRESULT STDMETHODCALLTYPE get_RowSpan(int *pRetVal); + HRESULT STDMETHODCALLTYPE get_ColumnSpan(int *pRetVal); + HRESULT STDMETHODCALLTYPE get_ContainingGrid(IRawElementProviderSimple **pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAGRIDITEMPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.cpp new file mode 100644 index 0000000000..65c2df703b --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiagridprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaGridProvider::QWindowsUiaGridProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaGridProvider::~QWindowsUiaGridProvider() +{ +} + +// Returns the provider for an item within a table/tree. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridProvider::GetItem(int row, int column, IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableInterface *tableInterface = accessible->tableInterface(); + if (!tableInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + if ((row >= 0) && (row < tableInterface->rowCount()) && (column >= 0) && (column < tableInterface->columnCount())) { + if (QAccessibleInterface *cell = tableInterface->cellAt(row, column)) { + *pRetVal = QWindowsUiaMainProvider::providerForAccessible(cell); + } + } + return S_OK; +} + +// Returns the number of rows. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridProvider::get_RowCount(int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableInterface *tableInterface = accessible->tableInterface(); + if (!tableInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = tableInterface->rowCount(); + return S_OK; +} + +// Returns the number of columns. +HRESULT STDMETHODCALLTYPE QWindowsUiaGridProvider::get_ColumnCount(int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableInterface *tableInterface = accessible->tableInterface(); + if (!tableInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = tableInterface->columnCount(); + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h new file mode 100644 index 0000000000..15521f98b3 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAGRIDPROVIDER_H +#define QWINDOWSUIAGRIDPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Grid control pattern provider. Used by tables/trees. +class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<IGridProvider> +{ + Q_DISABLE_COPY(QWindowsUiaGridProvider) +public: + explicit QWindowsUiaGridProvider(QAccessible::Id id); + virtual ~QWindowsUiaGridProvider(); + + // IGridProvider + HRESULT STDMETHODCALLTYPE GetItem(int row, int column, IRawElementProviderSimple **pRetVal); + HRESULT STDMETHODCALLTYPE get_RowCount(int *pRetVal); + HRESULT STDMETHODCALLTYPE get_ColumnCount(int *pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAGRIDPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.cpp new file mode 100644 index 0000000000..2af883c4f6 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiainvokeprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaInvokeProvider::QWindowsUiaInvokeProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaInvokeProvider::~QWindowsUiaInvokeProvider() +{ +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaInvokeProvider::Invoke() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + actionInterface->doAction(QAccessibleActionInterface::pressAction()); + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h new file mode 100644 index 0000000000..2b8a646983 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAINVOKEPROVIDER_H +#define QWINDOWSUIAINVOKEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Invoke control pattern provider. +class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<IInvokeProvider> +{ + Q_DISABLE_COPY(QWindowsUiaInvokeProvider) +public: + explicit QWindowsUiaInvokeProvider(QAccessible::Id id); + virtual ~QWindowsUiaInvokeProvider(); + + // IInvokeProvider + HRESULT STDMETHODCALLTYPE Invoke(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAINVOKEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp new file mode 100644 index 0000000000..46f73f81a0 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -0,0 +1,638 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiavalueprovider.h" +#include "qwindowsuiarangevalueprovider.h" +#include "qwindowsuiatextprovider.h" +#include "qwindowsuiatoggleprovider.h" +#include "qwindowsuiainvokeprovider.h" +#include "qwindowsuiaselectionprovider.h" +#include "qwindowsuiaselectionitemprovider.h" +#include "qwindowsuiatableprovider.h" +#include "qwindowsuiatableitemprovider.h" +#include "qwindowsuiagridprovider.h" +#include "qwindowsuiagriditemprovider.h" +#include "qwindowscombase.h" +#include "qwindowscontext.h" +#include "qwindowsuiautils.h" +#include "qwindowsuiaprovidercache.h" + +#include <QtCore/QDebug> +#include <QtGui/QAccessible> +#include <QtGui/QGuiApplication> +#include <QtGui/QWindow> + +#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) +#include <comdef.h> +#endif + +#include <QtCore/qt_windows.h> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +// Returns a cached instance of the provider for a specific acessible interface. +QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessibleInterface *accessible) +{ + if (!accessible) + return nullptr; + + QAccessible::Id id = QAccessible::uniqueId(accessible); + QWindowsUiaProviderCache *providerCache = QWindowsUiaProviderCache::instance(); + QWindowsUiaMainProvider *provider = qobject_cast<QWindowsUiaMainProvider *>(providerCache->providerForId(id)); + + if (provider) { + provider->AddRef(); + } else { + provider = new QWindowsUiaMainProvider(accessible); + providerCache->insert(id, provider); + } + return provider; +} + +QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount) + : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)), + m_ref(initialRefCount) +{ +} + +QWindowsUiaMainProvider::~QWindowsUiaMainProvider() +{ +} + +void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event) +{ + if (QAccessibleInterface *accessible = event->accessibleInterface()) { + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); + } + } +} + +void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *event) +{ + if (QAccessibleInterface *accessible = event->accessibleInterface()) { + if (event->changedStates().checked || event->changedStates().checkStateMixed) { + // Notifies states changes in checkboxes. + if (accessible->role() == QAccessible::CheckBox) { + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + VARIANT oldVal, newVal; + clearVariant(&oldVal); + int toggleState = ToggleState_Off; + if (accessible->state().checked) + toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On; + setVariantI4(toggleState, &newVal); + QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal); + } + } + } + if (event->changedStates().active) { + if (accessible->role() == QAccessible::Window) { + // Notifies window opened/closed. + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + if (accessible->state().active) { + QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId); + } else { + QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowClosedEventId); + } + } + } + } + } +} + +void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *event) +{ + if (QAccessibleInterface *accessible = event->accessibleInterface()) { + if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { + // Notifies changes in values of controls supporting the value interface. + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + VARIANT oldVal, newVal; + clearVariant(&oldVal); + setVariantDouble(valueInterface->currentValue().toDouble(), &newVal); + QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal); + } + } + } +} + +// Notifies changes in text content and selection state of text controls. +void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event) +{ + if (QAccessibleInterface *accessible = event->accessibleInterface()) { + if (accessible->textInterface()) { + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + if (event->type() == QAccessible::TextSelectionChanged) { + QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); + } else if (event->type() == QAccessible::TextCaretMoved) { + if (!accessible->state().readOnly) { + QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); + } + } else { + QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextChangedEventId); + } + } + } + } +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface) +{ + if (!iface) + return E_INVALIDARG; + *iface = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + + const bool result = qWindowsComQueryUnknownInterfaceMulti<IRawElementProviderSimple>(this, iid, iface) + || qWindowsComQueryInterface<IRawElementProviderSimple>(this, iid, iface) + || qWindowsComQueryInterface<IRawElementProviderFragment>(this, iid, iface) + || (accessible && hwndForAccessible(accessible) && qWindowsComQueryInterface<IRawElementProviderFragmentRoot>(this, iid, iface)); + return result ? S_OK : E_NOINTERFACE; +} + +ULONG QWindowsUiaMainProvider::AddRef() +{ + return ++m_ref; +} + +ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release() +{ + if (!--m_ref) { + delete this; + return 0; + } + return m_ref; +} + +HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal) +{ + if (!pRetVal) + return E_INVALIDARG; + // We are STA, (OleInitialize()). + *pRetVal = static_cast<ProviderOptions>(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading); + return S_OK; +} + +// Return providers for specific control patterns +HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknown **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idPattern; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + switch (idPattern) { + case UIA_TextPatternId: + case UIA_TextPattern2Id: + // All text controls. + if (accessible->textInterface()) { + *pRetVal = new QWindowsUiaTextProvider(id()); + } + break; + case UIA_ValuePatternId: + // All accessible controls return text(QAccessible::Value) (which may be empty). + *pRetVal = new QWindowsUiaValueProvider(id()); + break; + case UIA_RangeValuePatternId: + // Controls providing a numeric value within a range (e.g., sliders, scroll bars, dials). + if (accessible->valueInterface()) { + *pRetVal = new QWindowsUiaRangeValueProvider(id()); + } + break; + case UIA_TogglePatternId: + // Checkbox controls. + if (accessible->role() == QAccessible::CheckBox) { + *pRetVal = new QWindowsUiaToggleProvider(id()); + } + break; + case UIA_SelectionPatternId: + // Lists of items. + if (accessible->role() == QAccessible::List) { + *pRetVal = new QWindowsUiaSelectionProvider(id()); + } + break; + case UIA_SelectionItemPatternId: + // Items within a list and radio buttons. + if ((accessible->role() == QAccessible::RadioButton) + || (accessible->role() == QAccessible::ListItem)) { + *pRetVal = new QWindowsUiaSelectionItemProvider(id()); + } + break; + case UIA_TablePatternId: + // Table/tree. + if (accessible->tableInterface() + && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) { + *pRetVal = new QWindowsUiaTableProvider(id()); + } + break; + case UIA_TableItemPatternId: + // Item within a table/tree. + if (accessible->tableCellInterface() + && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) { + *pRetVal = new QWindowsUiaTableItemProvider(id()); + } + break; + case UIA_GridPatternId: + // Table/tree. + if (accessible->tableInterface() + && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) { + *pRetVal = new QWindowsUiaGridProvider(id()); + } + break; + case UIA_GridItemPatternId: + // Item within a table/tree. + if (accessible->tableCellInterface() + && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) { + *pRetVal = new QWindowsUiaGridItemProvider(id()); + } + break; + case UIA_InvokePatternId: + // Things that have an invokable action (e.g., simple buttons). + if (accessible->actionInterface()) { + *pRetVal = new QWindowsUiaInvokeProvider(id()); + } + break; + default: + break; + } + + return S_OK; +} + +HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp; + + if (!pRetVal) + return E_INVALIDARG; + clearVariant(pRetVal); + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + bool clientTopLevel = (accessible->role() == QAccessible::Client) + && accessible->parent() && (accessible->parent()->role() == QAccessible::Application); + + switch (idProp) { + case UIA_ProcessIdPropertyId: + // PID + setVariantI4(int(GetCurrentProcessId()), pRetVal); + break; + case UIA_AccessKeyPropertyId: + // Accelerator key. + setVariantString(accessible->text(QAccessible::Accelerator), pRetVal); + break; + case UIA_AutomationIdPropertyId: + // Automation ID, which can be used by tools to select a specific control in the UI. + setVariantString(automationIdForAccessible(accessible), pRetVal); + break; + case UIA_ClassNamePropertyId: + // Class name. + if (QObject *o = accessible->object()) { + QString className = QLatin1String(o->metaObject()->className()); + setVariantString(className, pRetVal); + } + break; + case UIA_FrameworkIdPropertyId: + setVariantString(QStringLiteral("Qt"), pRetVal); + break; + case UIA_ControlTypePropertyId: + if (clientTopLevel) { + // Reports a top-level widget as a window, instead of "custom". + setVariantI4(UIA_WindowControlTypeId, pRetVal); + } else { + // Control type converted from role. + setVariantI4(roleToControlTypeId(accessible->role()), pRetVal); + } + break; + case UIA_HelpTextPropertyId: + setVariantString(accessible->text(QAccessible::Help), pRetVal); + break; + case UIA_HasKeyboardFocusPropertyId: + setVariantBool(accessible->state().focused, pRetVal); + break; + case UIA_IsKeyboardFocusablePropertyId: + setVariantBool(accessible->state().focusable, pRetVal); + break; + case UIA_IsOffscreenPropertyId: + setVariantBool(false, pRetVal); + break; + case UIA_IsContentElementPropertyId: + setVariantBool(true, pRetVal); + break; + case UIA_IsControlElementPropertyId: + setVariantBool(true, pRetVal); + break; + case UIA_IsEnabledPropertyId: + setVariantBool(!accessible->state().disabled, pRetVal); + break; + case UIA_IsPasswordPropertyId: + setVariantBool(accessible->role() == QAccessible::EditableText + && accessible->state().passwordEdit, pRetVal); + break; + case UIA_IsPeripheralPropertyId: + // True for peripheral UIs. + if (QWindow *window = windowForAccessible(accessible)) { + const Qt::WindowType wt = window->type(); + setVariantBool(wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen, pRetVal); + } + break; + case UIA_FullDescriptionPropertyId: + setVariantString(accessible->text(QAccessible::Description), pRetVal); + break; + case UIA_NamePropertyId: { + QString name = accessible->text(QAccessible::Name); + if (name.isEmpty() && clientTopLevel) { + name = QCoreApplication::applicationName(); + } + setVariantString(name, pRetVal); + break; + } + default: + break; + } + return S_OK; +} + +// Generates an ID based on the name of the controls and their parents. +QString QWindowsUiaMainProvider::automationIdForAccessible(const QAccessibleInterface *accessible) +{ + QString result; + if (accessible) { + QObject *obj = accessible->object(); + while (obj) { + QString name = obj->objectName(); + if (name.isEmpty()) + return QString(); + if (!result.isEmpty()) + result.prepend(QLatin1Char('.')); + result.prepend(name); + obj = obj->parent(); + } + } + return result; +} + +HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + // Returns a host provider only for controls associated with a native window handle. Others should return NULL. + if (QAccessibleInterface *accessible = accessibleInterface()) { + if (HWND hwnd = hwndForAccessible(accessible)) { + return QWindowsUiaWrapper::instance()->hostProviderFromHwnd(hwnd, pRetVal); + } + } + return S_OK; +} + +// Navigates within the tree of accessible controls. +HRESULT QWindowsUiaMainProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << direction << " this: " << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleInterface *targetacc = nullptr; + + switch (direction) { + case NavigateDirection_Parent: + targetacc = accessible->parent(); + if (targetacc && (targetacc->role() == QAccessible::Application)) { + targetacc = nullptr; // The app's children are considered top level objects. + } + break; + case NavigateDirection_FirstChild: + targetacc = accessible->child(0); + break; + case NavigateDirection_LastChild: + targetacc = accessible->child(accessible->childCount() - 1); + break; + case NavigateDirection_NextSibling: + case NavigateDirection_PreviousSibling: + if (QAccessibleInterface *parent = accessible->parent()) { + if (parent->isValid()) { + int index = parent->indexOfChild(accessible); + index += (direction == NavigateDirection_NextSibling) ? 1 : -1; + if (index >= 0 && index < parent->childCount()) + targetacc = parent->child(index); + } + } + break; + } + + if (targetacc) + *pRetVal = providerForAccessible(targetacc); + return S_OK; +} + +// Returns a unique id assigned to the UI element, used as key by the UI Automation framework. +HRESULT QWindowsUiaMainProvider::GetRuntimeId(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + // The UiaAppendRuntimeId constant is used to make then ID unique + // among multiple instances running on the system. + int rtId[] = { UiaAppendRuntimeId, int(id()) }; + + if ((*pRetVal = SafeArrayCreateVector(VT_I4, 0, 2))) { + for (LONG i = 0; i < 2; ++i) + SafeArrayPutElement(*pRetVal, &i, &rtId[i]); + } + return S_OK; +} + +// Returns the bounding rectangle for the accessible control. +HRESULT QWindowsUiaMainProvider::get_BoundingRectangle(UiaRect *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QWindow *window = windowForAccessible(accessible); + if (!window) + return UIA_E_ELEMENTNOTAVAILABLE; + + rectToNativeUiaRect(accessible->rect(), window, pRetVal); + return S_OK; +} + +HRESULT QWindowsUiaMainProvider::GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + // No embedded roots. + return S_OK; +} + +// Sets focus to the control. +HRESULT QWindowsUiaMainProvider::SetFocus() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + actionInterface->doAction(QAccessibleActionInterface::setFocusAction()); + return S_OK; +} + +HRESULT QWindowsUiaMainProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + // Our UI Automation implementation considers the window as the root for + // non-native controls/fragments. + if (QAccessibleInterface *accessible = accessibleInterface()) { + if (QWindow *window = windowForAccessible(accessible)) { + if (QAccessibleInterface *rootacc = window->accessibleRoot()) { + *pRetVal = providerForAccessible(rootacc); + } + } + } + return S_OK; +} + +// Returns a provider for the UI element present at the specified screen coordinates. +HRESULT QWindowsUiaMainProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << x << y; + + if (!pRetVal) { + return E_INVALIDARG; + } + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QWindow *window = windowForAccessible(accessible); + if (!window) + return UIA_E_ELEMENTNOTAVAILABLE; + + // Scales coordinates from High DPI screens. + UiaPoint uiaPoint = {x, y}; + QPoint point; + nativeUiaPointToPoint(uiaPoint, window, &point); + + QAccessibleInterface *targetacc = accessible->childAt(point.x(), point.y()); + + if (targetacc) { + QAccessibleInterface *acc = targetacc; + // Controls can be embedded within grouping elements. By default returns the innermost control. + while (acc) { + targetacc = acc; + // For accessibility tools it may be better to return the text element instead of its subcomponents. + if (targetacc->textInterface()) break; + acc = acc->childAt(point.x(), point.y()); + } + *pRetVal = providerForAccessible(targetacc); + } + return S_OK; +} + +// Returns the fragment with focus. +HRESULT QWindowsUiaMainProvider::GetFocus(IRawElementProviderFragment **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + if (QAccessibleInterface *accessible = accessibleInterface()) { + if (QAccessibleInterface *focusacc = accessible->focusChild()) { + *pRetVal = providerForAccessible(focusacc); + } + } + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h new file mode 100644 index 0000000000..893cbf7f8a --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAMAINPROVIDER_H +#define QWINDOWSUIAMAINPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +#include <QtCore/QPointer> +#include <QtCore/QSharedPointer> +#include <QtCore/qt_windows.h> +#include <QtGui/QAccessible> + +QT_BEGIN_NAMESPACE + +// The main UI Automation class. +class QWindowsUiaMainProvider : + public QWindowsUiaBaseProvider, + public IRawElementProviderSimple, + public IRawElementProviderFragment, + public IRawElementProviderFragmentRoot +{ + Q_OBJECT + Q_DISABLE_COPY(QWindowsUiaMainProvider) +public: + static QWindowsUiaMainProvider *providerForAccessible(QAccessibleInterface *accessible); + explicit QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount = 1); + virtual ~QWindowsUiaMainProvider(); + static void notifyFocusChange(QAccessibleEvent *event); + static void notifyStateChange(QAccessibleStateChangeEvent *event); + static void notifyValueChange(QAccessibleValueChangeEvent *event); + static void notifyTextChange(QAccessibleEvent *event); + + // IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + // IRawElementProviderSimple methods + HRESULT STDMETHODCALLTYPE get_ProviderOptions(ProviderOptions *pRetVal); + HRESULT STDMETHODCALLTYPE GetPatternProvider(PATTERNID idPattern, IUnknown **pRetVal); + HRESULT STDMETHODCALLTYPE GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal); + HRESULT STDMETHODCALLTYPE get_HostRawElementProvider(IRawElementProviderSimple **pRetVal); + + // IRawElementProviderFragment methods + HRESULT STDMETHODCALLTYPE Navigate(NavigateDirection direction, IRawElementProviderFragment **pRetVal); + HRESULT STDMETHODCALLTYPE GetRuntimeId(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE get_BoundingRectangle(UiaRect *pRetVal); + HRESULT STDMETHODCALLTYPE GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE SetFocus(); + HRESULT STDMETHODCALLTYPE get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal); + + // IRawElementProviderFragmentRoot methods + HRESULT STDMETHODCALLTYPE ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal); + HRESULT STDMETHODCALLTYPE GetFocus(IRawElementProviderFragment **pRetVal); + +private: + QString automationIdForAccessible(const QAccessibleInterface *accessible); + ULONG m_ref; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAMAINPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp new file mode 100644 index 0000000000..9f0a1e126f --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiaprovidercache.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtCore/QDebug> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +// Private constructor +QWindowsUiaProviderCache::QWindowsUiaProviderCache() +{ +} + +// shared instance +QWindowsUiaProviderCache *QWindowsUiaProviderCache::instance() +{ + static QWindowsUiaProviderCache providerCache; + return &providerCache; +} + +// Returns the provider instance associated with the ID, or nullptr. +QWindowsUiaBaseProvider *QWindowsUiaProviderCache::providerForId(QAccessible::Id id) const +{ + return providerTable.value(id); +} + +// Inserts a provider in the cache and associates it with an accessibility ID. +void QWindowsUiaProviderCache::insert(QAccessible::Id id, QWindowsUiaBaseProvider *provider) +{ + remove(id); + if (provider) { + providerTable[id] = provider; + inverseTable[provider] = id; + // Connects the destroyed signal to our slot, to remove deleted objects from the cache. + QObject::connect(provider, &QObject::destroyed, this, &QWindowsUiaProviderCache::objectDestroyed); + } +} + +// Removes deleted provider objects from the cache. +void QWindowsUiaProviderCache::objectDestroyed(QObject *obj) +{ + // We have to use the inverse table to map the object address back to its ID, + // since at this point (called from QObject destructor), it has already been + // partially destroyed and we cannot treat it as a provider. + auto it = inverseTable.find(obj); + if (it != inverseTable.end()) { + providerTable.remove(*it); + inverseTable.remove(obj); + } +} + +// Removes a provider with a given id from the cache. +void QWindowsUiaProviderCache::remove(QAccessible::Id id) +{ + inverseTable.remove(providerTable.value(id)); + providerTable.remove(id); +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.h new file mode 100644 index 0000000000..7ad30ac39c --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAPROVIDERCACHE_H +#define QWINDOWSUIAPROVIDERCACHE_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +#include <QtCore/QHash> +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> + +QT_BEGIN_NAMESPACE + +// Singleton used to cache provider instances using the accessibility ID as the key. +class QWindowsUiaProviderCache : public QObject +{ + QWindowsUiaProviderCache(); + Q_OBJECT +public: + static QWindowsUiaProviderCache *instance(); + QWindowsUiaBaseProvider *providerForId(QAccessible::Id id) const; + void insert(QAccessible::Id id, QWindowsUiaBaseProvider *provider); + void remove(QAccessible::Id id); + +private Q_SLOTS: + void objectDestroyed(QObject *obj); + +private: + QHash<QAccessible::Id, QWindowsUiaBaseProvider *> providerTable; + QHash<QObject *, QAccessible::Id> inverseTable; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAPROVIDERCACHE_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.cpp new file mode 100644 index 0000000000..0cd09c3f0a --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiarangevalueprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaRangeValueProvider::QWindowsUiaRangeValueProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaRangeValueProvider::~QWindowsUiaRangeValueProvider() +{ +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::SetValue(double val) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleValueInterface *valueInterface = accessible->valueInterface(); + if (!valueInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + double minimum = valueInterface->minimumValue().toDouble(); + double maximum = valueInterface->maximumValue().toDouble(); + if ((val < minimum) || (val > maximum)) + return E_INVALIDARG; + + valueInterface->setCurrentValue(QVariant(val)); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::get_Value(double *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleValueInterface *valueInterface = accessible->valueInterface(); + if (!valueInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QVariant varValue = valueInterface->currentValue(); + *pRetVal = varValue.toDouble(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::get_IsReadOnly(BOOL *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = accessible->state().readOnly; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::get_Maximum(double *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleValueInterface *valueInterface = accessible->valueInterface(); + if (!valueInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QVariant varValue = valueInterface->maximumValue(); + *pRetVal = varValue.toDouble(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::get_Minimum(double *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleValueInterface *valueInterface = accessible->valueInterface(); + if (!valueInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QVariant varValue = valueInterface->minimumValue(); + *pRetVal = varValue.toDouble(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::get_LargeChange(double *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + return get_SmallChange(pRetVal); +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaRangeValueProvider::get_SmallChange(double *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleValueInterface *valueInterface = accessible->valueInterface(); + if (!valueInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QVariant varValue = valueInterface->minimumStepSize(); + *pRetVal = varValue.toDouble(); + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h new file mode 100644 index 0000000000..f742ef99c2 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIARANGEVALUEPROVIDER_H +#define QWINDOWSUIARANGEVALUEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Range Value control pattern provider. +class QWindowsUiaRangeValueProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<IRangeValueProvider> +{ + Q_DISABLE_COPY(QWindowsUiaRangeValueProvider) +public: + explicit QWindowsUiaRangeValueProvider(QAccessible::Id id); + virtual ~QWindowsUiaRangeValueProvider(); + + // IRangeValueProvider + HRESULT STDMETHODCALLTYPE SetValue(double val); + HRESULT STDMETHODCALLTYPE get_Value(double *pRetVal); + HRESULT STDMETHODCALLTYPE get_IsReadOnly(BOOL *pRetVal); + HRESULT STDMETHODCALLTYPE get_Maximum(double *pRetVal); + HRESULT STDMETHODCALLTYPE get_Minimum(double *pRetVal); + HRESULT STDMETHODCALLTYPE get_LargeChange(double *pRetVal); + HRESULT STDMETHODCALLTYPE get_SmallChange(double *pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIARANGEVALUEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp new file mode 100644 index 0000000000..45216a6d1c --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiaselectionitemprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaSelectionItemProvider::QWindowsUiaSelectionItemProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaSelectionItemProvider::~QWindowsUiaSelectionItemProvider() +{ +} + +// Selects the element (deselecting all others). +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::Select() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (accessible->role() == QAccessible::RadioButton) { + // For radio buttons we just invoke the selection action; others are automatically deselected. + actionInterface->doAction(QAccessibleActionInterface::pressAction()); + } else { + // Toggle list item if not already selected. It must be done first to support all selection modes. + if (!accessible->state().selected) { + actionInterface->doAction(QAccessibleActionInterface::toggleAction()); + } + // Toggle selected siblings. + if (QAccessibleInterface *parent = accessible->parent()) { + for (int i = 0; i < parent->childCount(); ++i) { + if (QAccessibleInterface *sibling = parent->child(i)) { + if ((sibling != accessible) && (sibling->state().selected)) { + if (QAccessibleActionInterface *siblingAction = sibling->actionInterface()) { + siblingAction->doAction(QAccessibleActionInterface::toggleAction()); + } + } + } + } + } + } + return S_OK; +} + +// Adds the element to the list of selected elements. +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::AddToSelection() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (accessible->role() == QAccessible::RadioButton) { + // For radio buttons we invoke the selection action. + actionInterface->doAction(QAccessibleActionInterface::pressAction()); + } else { + // Toggle list item if not already selected. + if (!accessible->state().selected) { + actionInterface->doAction(QAccessibleActionInterface::toggleAction()); + } + } + return S_OK; +} + +// Removes a list item from selection. +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::RemoveFromSelection() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (accessible->role() != QAccessible::RadioButton) { + if (accessible->state().selected) { + actionInterface->doAction(QAccessibleActionInterface::toggleAction()); + } + } + + return S_OK; +} + +// Returns true if element is currently selected. +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_IsSelected(BOOL *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = FALSE; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (accessible->role() == QAccessible::RadioButton) + *pRetVal = accessible->state().checked; + else + *pRetVal = accessible->state().selected; + return S_OK; +} + +// Returns the provider for the container element (e.g., the list for the list item). +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_SelectionContainer(IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + // Radio buttons do not require a container. + if (accessible->role() == QAccessible::ListItem) { + if (QAccessibleInterface *parent = accessible->parent()) { + if (parent->role() == QAccessible::List) { + *pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent); + } + } + } + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h new file mode 100644 index 0000000000..6a9b5b1e4b --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIASELECTIONITEMPROVIDER_H +#define QWINDOWSUIASELECTIONITEMPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Selection Item control pattern provider. Used for List items and radio buttons. +class QWindowsUiaSelectionItemProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<ISelectionItemProvider> +{ + Q_DISABLE_COPY(QWindowsUiaSelectionItemProvider) +public: + explicit QWindowsUiaSelectionItemProvider(QAccessible::Id id); + virtual ~QWindowsUiaSelectionItemProvider(); + + // ISelectionItemProvider + HRESULT STDMETHODCALLTYPE Select(); + HRESULT STDMETHODCALLTYPE AddToSelection(); + HRESULT STDMETHODCALLTYPE RemoveFromSelection(); + HRESULT STDMETHODCALLTYPE get_IsSelected(BOOL *pRetVal); + HRESULT STDMETHODCALLTYPE get_SelectionContainer(IRawElementProviderSimple **pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIASELECTIONITEMPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp new file mode 100644 index 0000000000..1c06503bfc --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiaselectionprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> +#include <QList> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaSelectionProvider::QWindowsUiaSelectionProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaSelectionProvider::~QWindowsUiaSelectionProvider() +{ +} + +// Returns an array of providers with the selected items. +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::GetSelection(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + // First put selected items in a list, then build a safe array with the right size. + QList<QAccessibleInterface *> selectedList; + for (int i = 0; i < accessible->childCount(); ++i) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (child->state().selected) { + selectedList.append(child); + } + } + } + + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, selectedList.size()))) { + for (LONG i = 0; i < selectedList.size(); ++i) { + if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(selectedList.at(i))) { + SafeArrayPutElement(*pRetVal, &i, static_cast<IRawElementProviderSimple *>(childProvider)); + childProvider->Release(); + } + } + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_CanSelectMultiple(BOOL *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = FALSE; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = accessible->state().multiSelectable; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired(BOOL *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = FALSE; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + // Initially returns false if none are selected. After the first selection, it may be required. + bool anySelected = false; + for (int i = 0; i < accessible->childCount(); ++i) { + if (QAccessibleInterface *child = accessible->child(i)) { + if (child->state().selected) { + anySelected = true; + break; + } + } + } + + *pRetVal = anySelected && !accessible->state().multiSelectable && !accessible->state().extSelectable; + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h new file mode 100644 index 0000000000..5a07a82ac8 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIASELECTIONPROVIDER_H +#define QWINDOWSUIASELECTIONPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Selection control pattern provider. Used for Lists. +class QWindowsUiaSelectionProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<ISelectionProvider> +{ + Q_DISABLE_COPY(QWindowsUiaSelectionProvider) +public: + explicit QWindowsUiaSelectionProvider(QAccessible::Id id); + virtual ~QWindowsUiaSelectionProvider(); + + // ISelectionProvider + HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(BOOL *pRetVal); + HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(BOOL *pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIASELECTIONPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.cpp new file mode 100644 index 0000000000..3ea29fc86c --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiatableitemprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaTableItemProvider::QWindowsUiaTableItemProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaTableItemProvider::~QWindowsUiaTableItemProvider() +{ +} + +// Returns the providers for the row headers associated with the item. +HRESULT STDMETHODCALLTYPE QWindowsUiaTableItemProvider::GetRowHeaderItems(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QList<QAccessibleInterface *> headers = tableCellInterface->rowHeaderCells(); + + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, headers.size()))) { + for (LONG i = 0; i < headers.size(); ++i) { + if (QWindowsUiaMainProvider *headerProvider = QWindowsUiaMainProvider::providerForAccessible(headers.at(i))) { + SafeArrayPutElement(*pRetVal, &i, static_cast<IRawElementProviderSimple *>(headerProvider)); + headerProvider->Release(); + } + } + } + return S_OK; +} + +// Returns the providers for the column headers associated with the item. +HRESULT STDMETHODCALLTYPE QWindowsUiaTableItemProvider::GetColumnHeaderItems(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableCellInterface *tableCellInterface = accessible->tableCellInterface(); + if (!tableCellInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QList<QAccessibleInterface *> headers = tableCellInterface->columnHeaderCells(); + + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, headers.size()))) { + for (LONG i = 0; i < headers.size(); ++i) { + if (QWindowsUiaMainProvider *headerProvider = QWindowsUiaMainProvider::providerForAccessible(headers.at(i))) { + SafeArrayPutElement(*pRetVal, &i, static_cast<IRawElementProviderSimple *>(headerProvider)); + headerProvider->Release(); + } + } + } + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h new file mode 100644 index 0000000000..277884c980 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIATABLEITEMPROVIDER_H +#define QWINDOWSUIATABLEITEMPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Table Item control pattern provider. Used by items within a table/tree. +class QWindowsUiaTableItemProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<ITableItemProvider> +{ + Q_DISABLE_COPY(QWindowsUiaTableItemProvider) +public: + explicit QWindowsUiaTableItemProvider(QAccessible::Id id); + virtual ~QWindowsUiaTableItemProvider(); + + // ITableItemProvider + HRESULT STDMETHODCALLTYPE GetRowHeaderItems(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE GetColumnHeaderItems(SAFEARRAY **pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIATABLEITEMPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.cpp new file mode 100644 index 0000000000..f79a24536b --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiatableprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaTableProvider::QWindowsUiaTableProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaTableProvider::~QWindowsUiaTableProvider() +{ +} + +// Gets the providers for all the row headers in the table. +HRESULT STDMETHODCALLTYPE QWindowsUiaTableProvider::GetRowHeaders(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableInterface *tableInterface = accessible->tableInterface(); + if (!tableInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QList<QAccessibleInterface *> headers; + + for (int i = 0; i < tableInterface->rowCount(); ++i) { + if (QAccessibleInterface *cell = tableInterface->cellAt(i, 0)) { + if (QAccessibleTableCellInterface *tableCellInterface = cell->tableCellInterface()) { + headers.append(tableCellInterface->rowHeaderCells()); + } + } + } + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, headers.size()))) { + for (LONG i = 0; i < headers.size(); ++i) { + if (QWindowsUiaMainProvider *headerProvider = QWindowsUiaMainProvider::providerForAccessible(headers.at(i))) { + SafeArrayPutElement(*pRetVal, &i, static_cast<IRawElementProviderSimple *>(headerProvider)); + headerProvider->Release(); + } + } + } + return S_OK; +} + +// Gets the providers for all the column headers in the table. +HRESULT STDMETHODCALLTYPE QWindowsUiaTableProvider::GetColumnHeaders(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTableInterface *tableInterface = accessible->tableInterface(); + if (!tableInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QList<QAccessibleInterface *> headers; + + for (int i = 0; i < tableInterface->columnCount(); ++i) { + if (QAccessibleInterface *cell = tableInterface->cellAt(0, i)) { + if (QAccessibleTableCellInterface *tableCellInterface = cell->tableCellInterface()) { + headers.append(tableCellInterface->columnHeaderCells()); + } + } + } + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, headers.size()))) { + for (LONG i = 0; i < headers.size(); ++i) { + if (QWindowsUiaMainProvider *headerProvider = QWindowsUiaMainProvider::providerForAccessible(headers.at(i))) { + SafeArrayPutElement(*pRetVal, &i, static_cast<IRawElementProviderSimple *>(headerProvider)); + headerProvider->Release(); + } + } + } + return S_OK; +} + +// Returns the primary direction of traversal for the table. +HRESULT STDMETHODCALLTYPE QWindowsUiaTableProvider::get_RowOrColumnMajor(enum RowOrColumnMajor *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = RowOrColumnMajor_Indeterminate; + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h new file mode 100644 index 0000000000..8cd0acda03 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIATABLEPROVIDER_H +#define QWINDOWSUIATABLEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Table control pattern provider. Used by tables/trees. +class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<ITableProvider> +{ + Q_DISABLE_COPY(QWindowsUiaTableProvider) +public: + explicit QWindowsUiaTableProvider(QAccessible::Id id); + virtual ~QWindowsUiaTableProvider(); + + // ITableProvider + HRESULT STDMETHODCALLTYPE GetRowHeaders(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE GetColumnHeaders(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE get_RowOrColumnMajor(enum RowOrColumnMajor *pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIATABLEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp new file mode 100644 index 0000000000..e1622933af --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiatextprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaTextProvider::QWindowsUiaTextProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaTextProvider::~QWindowsUiaTextProvider() +{ +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::QueryInterface(REFIID iid, LPVOID *iface) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!iface) + return E_INVALIDARG; + *iface = nullptr; + + const bool result = qWindowsComQueryUnknownInterfaceMulti<ITextProvider>(this, iid, iface) + || qWindowsComQueryInterface<ITextProvider>(this, iid, iface) + || qWindowsComQueryInterface<ITextProvider2>(this, iid, iface); + return result ? S_OK : E_NOINTERFACE; +} + +// Returns an array of providers for the selected text ranges. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::GetSelection(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + int selCount = textInterface->selectionCount(); + if (selCount > 0) { + // Build a safe array with the text range providers. + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, selCount))) { + for (LONG i = 0; i < selCount; ++i) { + int startOffset = 0, endOffset = 0; + textInterface->selection((int)i, &startOffset, &endOffset); + QWindowsUiaTextRangeProvider *textRangeProvider = new QWindowsUiaTextRangeProvider(id(), startOffset, endOffset); + SafeArrayPutElement(*pRetVal, &i, static_cast<IUnknown *>(textRangeProvider)); + textRangeProvider->Release(); + } + } + } else { + // If there is no selection, we return an array with a single degenerate (empty) text range at the cursor position. + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1))) { + LONG i = 0; + int cursorPosition = textInterface->cursorPosition(); + QWindowsUiaTextRangeProvider *textRangeProvider = new QWindowsUiaTextRangeProvider(id(), cursorPosition, cursorPosition); + SafeArrayPutElement(*pRetVal, &i, static_cast<IUnknown *>(textRangeProvider)); + textRangeProvider->Release(); + } + } + return S_OK; +} + +// Returns an array of providers for the visible text ranges. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::GetVisibleRanges(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + // Considering the entire text as visible. + if ((*pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1))) { + LONG i = 0; + QWindowsUiaTextRangeProvider *textRangeProvider = new QWindowsUiaTextRangeProvider(id(), 0, textInterface->characterCount()); + SafeArrayPutElement(*pRetVal, &i, static_cast<IUnknown *>(textRangeProvider)); + textRangeProvider->Release(); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::RangeFromChild(IRawElementProviderSimple * /*childElement*/, + ITextRangeProvider **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + // No children supported. + return S_OK; +} + +// Returns a degenerate text range at the specified point. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::RangeFromPoint(UiaPoint point, ITextRangeProvider **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QWindow *window = windowForAccessible(accessible); + if (!window) + return UIA_E_ELEMENTNOTAVAILABLE; + + QPoint pt; + nativeUiaPointToPoint(point, window, &pt); + + int offset = textInterface->offsetAtPoint(pt); + if ((offset >= 0) && (offset < textInterface->characterCount())) { + *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset); + } + return S_OK; +} + +// Returns a text range provider for the entire text. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::get_DocumentRange(ITextRangeProvider **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = new QWindowsUiaTextRangeProvider(id(), 0, textInterface->characterCount()); + return S_OK; +} + +// Currently supporting single selection. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::get_SupportedTextSelection(SupportedTextSelection *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = SupportedTextSelection_Single; + return S_OK; +} + +// Not supporting annotations. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::RangeFromAnnotation(IRawElementProviderSimple * /*annotationElement*/, ITextRangeProvider **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::GetCaretRange(BOOL *isActive, ITextRangeProvider **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!isActive || !pRetVal) + return E_INVALIDARG; + *isActive = FALSE; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + *isActive = accessible->state().focused; + + int cursorPosition = textInterface->cursorPosition(); + *pRetVal = new QWindowsUiaTextRangeProvider(id(), cursorPosition, cursorPosition); + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h new file mode 100644 index 0000000000..a6d10027fa --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIATEXTPROVIDER_H +#define QWINDOWSUIATEXTPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" +#include "qwindowsuiatextrangeprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Text control pattern provider. Used for text controls. +class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<ITextProvider2> +{ + Q_DISABLE_COPY(QWindowsUiaTextProvider) +public: + explicit QWindowsUiaTextProvider(QAccessible::Id id); + ~QWindowsUiaTextProvider(); + + // IUnknown overrides + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface); + + // ITextProvider + HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE GetVisibleRanges(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE RangeFromChild(IRawElementProviderSimple *childElement, ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE RangeFromPoint(UiaPoint point, ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE get_DocumentRange(ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE get_SupportedTextSelection(SupportedTextSelection *pRetVal); + + // ITextProvider2 + HRESULT STDMETHODCALLTYPE RangeFromAnnotation(IRawElementProviderSimple *annotationElement, ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE GetCaretRange(BOOL *isActive, ITextRangeProvider **pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIATEXTPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp new file mode 100644 index 0000000000..dae7cbdd5f --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp @@ -0,0 +1,554 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiatextrangeprovider.h" +#include "qwindowsuiamainprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaTextRangeProvider::QWindowsUiaTextRangeProvider(QAccessible::Id id, int startOffset, int endOffset) : + QWindowsUiaBaseProvider(id), + m_startOffset(startOffset), + m_endOffset(endOffset) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this << startOffset << endOffset; +} + +QWindowsUiaTextRangeProvider::~QWindowsUiaTextRangeProvider() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; +} + +HRESULT QWindowsUiaTextRangeProvider::AddToSelection() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + return Select(); +} + +HRESULT QWindowsUiaTextRangeProvider::Clone(ITextRangeProvider **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + + *pRetVal = new QWindowsUiaTextRangeProvider(id(), m_startOffset, m_endOffset); + return S_OK; +} + +// Two ranges are considered equal if their start/end points are the same. +HRESULT QWindowsUiaTextRangeProvider::Compare(ITextRangeProvider *range, BOOL *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!range || !pRetVal) + return E_INVALIDARG; + + QWindowsUiaTextRangeProvider *targetProvider = static_cast<QWindowsUiaTextRangeProvider *>(range); + *pRetVal = ((targetProvider->m_startOffset == m_startOffset) && (targetProvider->m_endOffset == m_endOffset)); + return S_OK; +} + +// Compare different endpoinds between two providers. +HRESULT QWindowsUiaTextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint, + ITextRangeProvider *targetRange, + TextPatternRangeEndpoint targetEndpoint, + int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ + << "endpoint=" << endpoint << "targetRange=" << targetRange + << "targetEndpoint=" << targetEndpoint << "this: " << this; + + if (!targetRange || !pRetVal) + return E_INVALIDARG; + + QWindowsUiaTextRangeProvider *targetProvider = static_cast<QWindowsUiaTextRangeProvider *>(targetRange); + + int point = (endpoint == TextPatternRangeEndpoint_Start) ? m_startOffset : m_endOffset; + int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ? + targetProvider->m_startOffset : targetProvider->m_endOffset; + *pRetVal = point - targetPoint; + return S_OK; +} + +// Expands/normalizes the range for a given text unit. +HRESULT QWindowsUiaTextRangeProvider::ExpandToEnclosingUnit(TextUnit unit) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "unit=" << unit << "this: " << this; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + int len = textInterface->characterCount(); + if (len < 1) { + m_startOffset = 0; + m_endOffset = 0; + } else { + if (unit == TextUnit_Character) { + m_startOffset = qBound(0, m_startOffset, len - 1); + m_endOffset = m_startOffset + 1; + } else { + QString text = textInterface->text(0, len); + for (int t = m_startOffset; t >= 0; --t) { + if (!isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) { + m_startOffset = t; + break; + } + } + for (int t = m_startOffset; t < len; ++t) { + if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) { + m_endOffset = t + 1; + break; + } + } + } + } + return S_OK; +} + +// Not supported. +HRESULT QWindowsUiaTextRangeProvider::FindAttribute(TEXTATTRIBUTEID /* attributeId */, + VARIANT /* val */, BOOL /* backward */, + ITextRangeProvider **pRetVal) +{ + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + return S_OK; +} + +// Returns the value of a given attribute. +HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTATTRIBUTEID attributeId, + VARIANT *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "attributeId=" << attributeId << "this: " << this; + + if (!pRetVal) + return E_INVALIDARG; + clearVariant(pRetVal); + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + switch (attributeId) { + case UIA_IsReadOnlyAttributeId: + setVariantBool(accessible->state().readOnly, pRetVal); + break; + case UIA_CaretPositionAttributeId: + if (textInterface->cursorPosition() == 0) + setVariantI4(CaretPosition_BeginningOfLine, pRetVal); + else if (textInterface->cursorPosition() == textInterface->characterCount()) + setVariantI4(CaretPosition_EndOfLine, pRetVal); + else + setVariantI4(CaretPosition_Unknown, pRetVal); + break; + default: + break; + } + return S_OK; +} + +// Returns an array of bounding rectangles for text lines within the range. +HRESULT QWindowsUiaTextRangeProvider::GetBoundingRectangles(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + QWindow *window = windowForAccessible(accessible); + if (!window) + return UIA_E_ELEMENTNOTAVAILABLE; + + int len = textInterface->characterCount(); + QList<QRect> rectList; + + if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset)) { + int start, end; + textInterface->textAtOffset(m_startOffset, QAccessible::LineBoundary, &start, &end); + while ((start >= 0) && (end >= 0)) { + int startRange = qMax(start, m_startOffset); + int endRange = qMin(end, m_endOffset); + if (startRange < endRange) { + // Calculates a bounding rectangle for the line and adds it to the list. + QRect startRect = textInterface->characterRect(startRange); + QRect endRect = textInterface->characterRect(endRange - 1); + QRect lineRect(qMin(startRect.x(), endRect.x()), + qMin(startRect.y(), endRect.y()), + qMax(startRect.x() + startRect.width(), endRect.x() + endRect.width()) - qMin(startRect.x(), endRect.x()), + qMax(startRect.y() + startRect.height(), endRect.y() + endRect.height()) - qMin(startRect.y(), endRect.y())); + rectList.append(lineRect); + } + if (end >= len) break; + textInterface->textAfterOffset(end + 1, QAccessible::LineBoundary, &start, &end); + } + } + + if ((*pRetVal = SafeArrayCreateVector(VT_R8, 0, 4 * rectList.size()))) { + for (int i = 0; i < rectList.size(); ++i) { + // Scale rect for high DPI screens. + UiaRect uiaRect; + rectToNativeUiaRect(rectList[i], window, &uiaRect); + double coords[4] = { uiaRect.left, uiaRect.top, uiaRect.width, uiaRect.height }; + for (int j = 0; j < 4; ++j) { + LONG idx = 4 * i + j; + SafeArrayPutElement(*pRetVal, &idx, &coords[j]); + } + } + } + return S_OK; +} + +// Returns an array of children elements embedded within the range. +HRESULT QWindowsUiaTextRangeProvider::GetChildren(SAFEARRAY **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + // Not supporting any children. + *pRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 0); + return S_OK; +} + +// Returns a provider for the enclosing element (text to which the range belongs). +HRESULT QWindowsUiaTextRangeProvider::GetEnclosingElement(IRawElementProviderSimple **pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + if (!pRetVal) + return E_INVALIDARG; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = QWindowsUiaMainProvider::providerForAccessible(accessible); + return S_OK; +} + +// Gets the text within the range. +HRESULT QWindowsUiaTextRangeProvider::GetText(int maxLength, BSTR *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << maxLength << "this: " << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + int len = textInterface->characterCount(); + QString rangeText; + if ((m_startOffset >= 0) && (m_endOffset <= len) && (m_startOffset < m_endOffset)) + rangeText = textInterface->text(m_startOffset, m_endOffset); + + if ((maxLength > -1) && (rangeText.size() > maxLength)) + rangeText.truncate(maxLength); + *pRetVal = bStrFromQString(rangeText); + return S_OK; +} + +// Moves the range a specified number of units (and normalizes it). +HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit, int count, int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "unit=" << unit << "count=" << count << "this: " << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + int len = textInterface->characterCount(); + + if (len < 1) + return S_OK; + + if (unit == TextUnit_Character) { + // Moves the start point, ensuring it lies within the bounds. + int start = qBound(0, m_startOffset + count, len - 1); + // If range was initially empty, leaves it as is; otherwise, normalizes it to one char. + m_endOffset = (m_endOffset > m_startOffset) ? start + 1 : start; + *pRetVal = start - m_startOffset; // Returns the actually moved distance. + m_startOffset = start; + } else { + if (count > 0) { + MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal); + MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal); + } else { + MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count, pRetVal); + MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, count, pRetVal); + } + } + return S_OK; +} + +// Copies the value of an end point from one range to another. +HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint, + ITextRangeProvider *targetRange, + TextPatternRangeEndpoint targetEndpoint) +{ + if (!targetRange) + return E_INVALIDARG; + + qCDebug(lcQpaUiAutomation) << __FUNCTION__ + << "endpoint=" << endpoint << "targetRange=" << targetRange << "targetEndpoint=" << targetEndpoint << "this: " << this; + + QWindowsUiaTextRangeProvider *targetProvider = static_cast<QWindowsUiaTextRangeProvider *>(targetRange); + + int targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start) ? + targetProvider->m_startOffset : targetProvider->m_endOffset; + + // If the moved endpoint crosses the other endpoint, that one is moved too. + if (endpoint == TextPatternRangeEndpoint_Start) { + m_startOffset = targetPoint; + if (m_endOffset < m_startOffset) + m_endOffset = m_startOffset; + } else { + m_endOffset = targetPoint; + if (m_endOffset < m_startOffset) + m_startOffset = m_endOffset; + } + return S_OK; +} + +// Moves an endpoint an specific number of units. +HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, + TextUnit unit, int count, + int *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ + << "endpoint=" << endpoint << "unit=" << unit << "count=" << count << "this: " << this; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = 0; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + int len = textInterface->characterCount(); + + if (len < 1) + return S_OK; + + if (unit == TextUnit_Character) { + if (endpoint == TextPatternRangeEndpoint_Start) { + int boundedValue = qBound(0, m_startOffset + count, len - 1); + *pRetVal = boundedValue - m_startOffset; + m_startOffset = boundedValue; + m_endOffset = qBound(m_startOffset, m_endOffset, len); + } else { + int boundedValue = qBound(0, m_endOffset + count, len); + *pRetVal = boundedValue - m_endOffset; + m_endOffset = boundedValue; + m_startOffset = qBound(0, m_startOffset, m_endOffset); + } + } else { + QString text = textInterface->text(0, len); + int moved = 0; + + if (endpoint == TextPatternRangeEndpoint_Start) { + if (count > 0) { + for (int t = m_startOffset; (t < len - 1) && (moved < count); ++t) { + if (isTextUnitSeparator(unit, text[t]) && !isTextUnitSeparator(unit, text[t + 1])) { + m_startOffset = t + 1; + ++moved; + } + } + m_endOffset = qBound(m_startOffset, m_endOffset, len); + } else { + for (int t = m_startOffset - 1; (t >= 0) && (moved > count); --t) { + if (!isTextUnitSeparator(unit, text[t]) && ((t == 0) || isTextUnitSeparator(unit, text[t - 1]))) { + m_startOffset = t; + --moved; + } + } + } + } else { + if (count > 0) { + for (int t = m_endOffset; (t < len) && (moved < count); ++t) { + if ((t == len - 1) || (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1])))) { + m_endOffset = t + 1; + ++moved; + } + } + } else { + int end = 0; + for (int t = m_endOffset - 2; (t > 0) && (moved > count); --t) { + if (isTextUnitSeparator(unit, text[t]) && ((unit == TextUnit_Word) || !isTextUnitSeparator(unit, text[t + 1]))) { + end = t + 1; + --moved; + } + } + m_endOffset = end; + m_startOffset = qBound(0, m_startOffset, m_endOffset); + } + } + *pRetVal = moved; + } + return S_OK; +} + +HRESULT QWindowsUiaTextRangeProvider::RemoveFromSelection() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + // unselects all + return unselect(); +} + +// Scrolls the range into view. +HRESULT QWindowsUiaTextRangeProvider::ScrollIntoView(BOOL alignToTop) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << "alignToTop=" << alignToTop << "this: " << this; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + textInterface->scrollToSubstring(m_startOffset, m_endOffset); + return S_OK; +} + +// Selects the range. +HRESULT QWindowsUiaTextRangeProvider::Select() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + // unselects all and adds a new selection + unselect(); + textInterface->addSelection(m_startOffset, m_endOffset); + return S_OK; +} + +// Not supported. +HRESULT QWindowsUiaTextRangeProvider::FindTextW(BSTR /* text */, BOOL /* backward */, + BOOL /* ignoreCase */, + ITextRangeProvider **pRetVal) +{ + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + return S_OK; +} + +// Removes all selected ranges from the text element. +HRESULT QWindowsUiaTextRangeProvider::unselect() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleTextInterface *textInterface = accessible->textInterface(); + if (!textInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + int selCount = textInterface->selectionCount(); + + for (int i = selCount - 1; i >= 0; --i) + textInterface->removeSelection(i); + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h new file mode 100644 index 0000000000..6fe6502c41 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h @@ -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$ +** +****************************************************************************/ + +#ifndef QWINDOWSUIATEXTRANGEPROVIDER_H +#define QWINDOWSUIATEXTRANGEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Text Range control pattern provider. Used for text controls. +class QWindowsUiaTextRangeProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<ITextRangeProvider> +{ + Q_DISABLE_COPY(QWindowsUiaTextRangeProvider) +public: + explicit QWindowsUiaTextRangeProvider(QAccessible::Id id, int startOffset, int endOffset); + virtual ~QWindowsUiaTextRangeProvider(); + + HRESULT STDMETHODCALLTYPE AddToSelection(); + HRESULT STDMETHODCALLTYPE Clone(ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE Compare(ITextRangeProvider *range, BOOL *pRetVal); + HRESULT STDMETHODCALLTYPE CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider *targetRange, TextPatternRangeEndpoint targetEndpoint, int *pRetVal); + HRESULT STDMETHODCALLTYPE ExpandToEnclosingUnit(TextUnit unit); + HRESULT STDMETHODCALLTYPE FindAttribute(TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE FindText(BSTR text, BOOL backward, BOOL ignoreCase, ITextRangeProvider **pRetVal); + HRESULT STDMETHODCALLTYPE GetAttributeValue(TEXTATTRIBUTEID attributeId, VARIANT *pRetVal); + HRESULT STDMETHODCALLTYPE GetBoundingRectangles(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE GetChildren(SAFEARRAY **pRetVal); + HRESULT STDMETHODCALLTYPE GetEnclosingElement(IRawElementProviderSimple **pRetVal); + HRESULT STDMETHODCALLTYPE GetText(int maxLength, BSTR *pRetVal); + HRESULT STDMETHODCALLTYPE Move(TextUnit unit, int count, int *pRetVal); + HRESULT STDMETHODCALLTYPE MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider *targetRange, TextPatternRangeEndpoint targetEndpoint); + HRESULT STDMETHODCALLTYPE MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int *pRetVal); + HRESULT STDMETHODCALLTYPE RemoveFromSelection(); + HRESULT STDMETHODCALLTYPE ScrollIntoView(BOOL alignToTop); + HRESULT STDMETHODCALLTYPE Select(); + +private: + HRESULT unselect(); + int m_startOffset; + int m_endOffset; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIATEXTRANGEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.cpp new file mode 100644 index 0000000000..01cdfd7e91 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiatoggleprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaToggleProvider::QWindowsUiaToggleProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaToggleProvider::~QWindowsUiaToggleProvider() +{ +} + +// toggles the state by invoking the toggle action +HRESULT STDMETHODCALLTYPE QWindowsUiaToggleProvider::Toggle() +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + QAccessibleActionInterface *actionInterface = accessible->actionInterface(); + if (!actionInterface) + return UIA_E_ELEMENTNOTAVAILABLE; + + actionInterface->doAction(QAccessibleActionInterface::toggleAction()); + return S_OK; +} + +// Gets the current toggle state. +HRESULT STDMETHODCALLTYPE QWindowsUiaToggleProvider::get_ToggleState(ToggleState *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = ToggleState_Off; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + if (accessible->state().checked) + *pRetVal = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On; + else + *pRetVal = ToggleState_Off; + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h new file mode 100644 index 0000000000..a0df983e40 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIATOGGLEPROVIDER_H +#define QWINDOWSUIATOGGLEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Toggle control pattern provider. Used for checkboxes. +class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<IToggleProvider> +{ + Q_DISABLE_COPY(QWindowsUiaToggleProvider) +public: + explicit QWindowsUiaToggleProvider(QAccessible::Id id); + virtual ~QWindowsUiaToggleProvider(); + + // IToggleProvider + HRESULT STDMETHODCALLTYPE Toggle(); + HRESULT STDMETHODCALLTYPE get_ToggleState(ToggleState *pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIATOGGLEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp new file mode 100644 index 0000000000..89e5075dcb --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" +#include "qwindowswindow.h" + +#include <QtGui/QWindow> +#include <QtGui/private/qhighdpiscaling_p.h> +#include <cmath> + +QT_BEGIN_NAMESPACE + +namespace QWindowsUiAutomation { + +// Returns the window containing the element (usually the top window), +QWindow *windowForAccessible(const QAccessibleInterface *accessible) +{ + QWindow *window = accessible->window(); + if (!window) { + QAccessibleInterface *acc = accessible->parent(); + while (acc && acc->isValid() && !window) { + window = acc->window(); + QAccessibleInterface *par = acc->parent(); + acc = par; + } + } + return window; +} + +// Returns the native window handle associated with the element, if any. +// Usually it will be NULL, as Qt5 by default uses alien widgets with no native windows. +HWND hwndForAccessible(const QAccessibleInterface *accessible) +{ + if (QWindow *window = accessible->window()) { + if (!accessible->parent() || (accessible->parent()->window() != window)) { + return QWindowsBaseWindow::handleOf(window); + } + } + return NULL; +} + +void clearVariant(VARIANT *variant) +{ + variant->vt = VT_EMPTY; + variant->punkVal = nullptr; +} + +void setVariantI4(int value, VARIANT *variant) +{ + variant->vt = VT_I4; + variant->lVal = value; +} + +void setVariantBool(bool value, VARIANT *variant) +{ + variant->vt = VT_BOOL; + variant->boolVal = value ? -1 : 0; +} + +void setVariantDouble(double value, VARIANT *variant) +{ + variant->vt = VT_R8; + variant->boolVal = value; +} + +BSTR bStrFromQString(const QString &value) +{ + return SysAllocString(reinterpret_cast<const wchar_t *>(value.utf16())); +} + +void setVariantString(const QString &value, VARIANT *variant) +{ + variant->vt = VT_BSTR; + variant->bstrVal = bStrFromQString(value); +} + +// Scales a rect to native coordinates, according to high dpi settings. +void rectToNativeUiaRect(const QRect &rect, const QWindow *w, UiaRect *uiaRect) +{ + if (w && uiaRect) { + const qreal factor = QHighDpiScaling::factor(w); + uiaRect->left = qreal(rect.x()) * factor; + uiaRect->top = qreal(rect.y()) * factor; + uiaRect->width = qreal(rect.width()) * factor; + uiaRect->height = qreal(rect.height()) * factor; + } +} + +// Scales a point from native coordinates, according to high dpi settings. +void nativeUiaPointToPoint(const UiaPoint &uiaPoint, const QWindow *w, QPoint *point) +{ + if (w && point) { + const qreal factor = QHighDpiScaling::factor(w); + point->setX(int(std::lround(uiaPoint.x / factor))); + point->setY(int(std::lround(uiaPoint.y / factor))); + } +} + +// Maps an accessibility role ID to an UI Automation control type ID. +long roleToControlTypeId(QAccessible::Role role) +{ + static const QHash<QAccessible::Role, long> mapping { + {QAccessible::TitleBar, UIA_TitleBarControlTypeId}, + {QAccessible::MenuBar, UIA_MenuBarControlTypeId}, + {QAccessible::ScrollBar, UIA_ScrollBarControlTypeId}, + {QAccessible::Grip, UIA_ThumbControlTypeId}, + {QAccessible::Sound, UIA_CustomControlTypeId}, + {QAccessible::Cursor, UIA_CustomControlTypeId}, + {QAccessible::Caret, UIA_CustomControlTypeId}, + {QAccessible::AlertMessage, UIA_CustomControlTypeId}, + {QAccessible::Window, UIA_WindowControlTypeId}, + {QAccessible::Client, UIA_CustomControlTypeId}, + {QAccessible::PopupMenu, UIA_MenuControlTypeId}, + {QAccessible::MenuItem, UIA_MenuItemControlTypeId}, + {QAccessible::ToolTip, UIA_ToolTipControlTypeId}, + {QAccessible::Application, UIA_CustomControlTypeId}, + {QAccessible::Document, UIA_DocumentControlTypeId}, + {QAccessible::Pane, UIA_PaneControlTypeId}, + {QAccessible::Chart, UIA_CustomControlTypeId}, + {QAccessible::Dialog, UIA_WindowControlTypeId}, + {QAccessible::Border, UIA_CustomControlTypeId}, + {QAccessible::Grouping, UIA_GroupControlTypeId}, + {QAccessible::Separator, UIA_SeparatorControlTypeId}, + {QAccessible::ToolBar, UIA_ToolBarControlTypeId}, + {QAccessible::StatusBar, UIA_StatusBarControlTypeId}, + {QAccessible::Table, UIA_TableControlTypeId}, + {QAccessible::ColumnHeader, UIA_HeaderControlTypeId}, + {QAccessible::RowHeader, UIA_HeaderControlTypeId}, + {QAccessible::Column, UIA_HeaderItemControlTypeId}, + {QAccessible::Row, UIA_HeaderItemControlTypeId}, + {QAccessible::Cell, UIA_DataItemControlTypeId}, + {QAccessible::Link, UIA_HyperlinkControlTypeId}, + {QAccessible::HelpBalloon, UIA_ToolTipControlTypeId}, + {QAccessible::Assistant, UIA_CustomControlTypeId}, + {QAccessible::List, UIA_ListControlTypeId}, + {QAccessible::ListItem, UIA_ListItemControlTypeId}, + {QAccessible::Tree, UIA_TreeControlTypeId}, + {QAccessible::TreeItem, UIA_TreeItemControlTypeId}, + {QAccessible::PageTab, UIA_TabItemControlTypeId}, + {QAccessible::PropertyPage, UIA_CustomControlTypeId}, + {QAccessible::Indicator, UIA_CustomControlTypeId}, + {QAccessible::Graphic, UIA_ImageControlTypeId}, + {QAccessible::StaticText, UIA_EditControlTypeId}, + {QAccessible::EditableText, UIA_EditControlTypeId}, + {QAccessible::Button, UIA_ButtonControlTypeId}, + {QAccessible::CheckBox, UIA_CheckBoxControlTypeId}, + {QAccessible::RadioButton, UIA_RadioButtonControlTypeId}, + {QAccessible::ComboBox, UIA_ComboBoxControlTypeId}, + {QAccessible::ProgressBar, UIA_ProgressBarControlTypeId}, + {QAccessible::Dial, UIA_CustomControlTypeId}, + {QAccessible::HotkeyField, UIA_CustomControlTypeId}, + {QAccessible::Slider, UIA_SliderControlTypeId}, + {QAccessible::SpinBox, UIA_SpinnerControlTypeId}, + {QAccessible::Canvas, UIA_CustomControlTypeId}, + {QAccessible::Animation, UIA_CustomControlTypeId}, + {QAccessible::Equation, UIA_CustomControlTypeId}, + {QAccessible::ButtonDropDown, UIA_ButtonControlTypeId}, + {QAccessible::ButtonMenu, UIA_ButtonControlTypeId}, + {QAccessible::ButtonDropGrid, UIA_ButtonControlTypeId}, + {QAccessible::Whitespace, UIA_CustomControlTypeId}, + {QAccessible::PageTabList, UIA_TabControlTypeId}, + {QAccessible::Clock, UIA_CustomControlTypeId}, + {QAccessible::Splitter, UIA_CustomControlTypeId}, + }; + + return mapping.value(role, UIA_CustomControlTypeId); +} + +// True if a character can be a separator for a text unit. +bool isTextUnitSeparator(TextUnit unit, const QChar &ch) +{ + return (((unit == TextUnit_Word) || (unit == TextUnit_Format)) && ch.isSpace()) + || ((unit == TextUnit_Line) && (ch.toLatin1() == '\n')); +} + +} // namespace QWindowsUiAutomation + + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h new file mode 100644 index 0000000000..15f4d6e8ba --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAUTILS_H +#define QWINDOWSUIAUTILS_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include <QtCore/QString> +#include <QtCore/qt_windows.h> +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtGui/QWindow> +#include <QtCore/QDebug> +#include <QtCore/QRect> +#include <QtWindowsUIAutomationSupport/private/qwindowsuiawrapper_p.h> + +QT_BEGIN_NAMESPACE + +namespace QWindowsUiAutomation { + +QWindow *windowForAccessible(const QAccessibleInterface *accessible); + +HWND hwndForAccessible(const QAccessibleInterface *accessible); + +void rectToNativeUiaRect(const QRect &rect, const QWindow *w, UiaRect *uiaRect); + +void nativeUiaPointToPoint(const UiaPoint &uiaPoint, const QWindow *w, QPoint *point); + +long roleToControlTypeId(QAccessible::Role role); + +bool isTextUnitSeparator(TextUnit unit, const QChar &ch); + +void clearVariant(VARIANT *variant); + +void setVariantI4(int value, VARIANT *variant); + +void setVariantBool(bool value, VARIANT *variant); + +void setVariantDouble(double value, VARIANT *variant); + +BSTR bStrFromQString(const QString &value); + +void setVariantString(const QString &value, VARIANT *variant); + +} // namespace QWindowsUiAutomation + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAUTILS_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.cpp new file mode 100644 index 0000000000..ef7d564e22 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** 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 <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiavalueprovider.h" +#include "qwindowsuiautils.h" +#include "qwindowscontext.h" + +#include <QtGui/QAccessible> +#include <QtGui/QAccessibleInterface> +#include <QtCore/QDebug> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsUiAutomation; + + +QWindowsUiaValueProvider::QWindowsUiaValueProvider(QAccessible::Id id) : + QWindowsUiaBaseProvider(id) +{ +} + +QWindowsUiaValueProvider::~QWindowsUiaValueProvider() +{ +} + +// Sets the value associated with the control. +HRESULT STDMETHODCALLTYPE QWindowsUiaValueProvider::SetValue(LPCWSTR val) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + // First sets the value as a text. + QString strVal = QString::fromUtf16(reinterpret_cast<const ushort *>(val)); + accessible->setText(QAccessible::Value, strVal); + + // Then, if the control supports the value interface (range value) + // and the supplied text can be converted to a number, and that number + // lies within the min/max limits, sets it as the control's current (numeric) value. + if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { + bool ok = false; + double numval = strVal.toDouble(&ok); + if (ok) { + double minimum = valueInterface->minimumValue().toDouble(); + double maximum = valueInterface->maximumValue().toDouble(); + if ((numval >= minimum) && (numval <= maximum)) { + valueInterface->setCurrentValue(QVariant(numval)); + } + } + } + return S_OK; +} + +// True for read-only controls. +HRESULT STDMETHODCALLTYPE QWindowsUiaValueProvider::get_IsReadOnly(BOOL *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = FALSE; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = accessible->state().readOnly; + return S_OK; +} + +// Returns the value in text form. +HRESULT STDMETHODCALLTYPE QWindowsUiaValueProvider::get_Value(BSTR *pRetVal) +{ + qCDebug(lcQpaUiAutomation) << __FUNCTION__; + + if (!pRetVal) + return E_INVALIDARG; + *pRetVal = nullptr; + + QAccessibleInterface *accessible = accessibleInterface(); + if (!accessible) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = bStrFromQString(accessible->text(QAccessible::Value)); + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h new file mode 100644 index 0000000000..db54fc0a46 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 QWINDOWSUIAVALUEPROVIDER_H +#define QWINDOWSUIAVALUEPROVIDER_H + +#include <QtCore/QtConfig> +#ifndef QT_NO_ACCESSIBILITY + +#include "qwindowsuiabaseprovider.h" + +QT_BEGIN_NAMESPACE + +// Implements the Value control pattern provider. +// Supported for all controls that can return text(QAccessible::Value). +class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider, + public QWindowsComBase<IValueProvider> +{ + Q_DISABLE_COPY(QWindowsUiaValueProvider) +public: + explicit QWindowsUiaValueProvider(QAccessible::Id id); + virtual ~QWindowsUiaValueProvider(); + + // IValueProvider + HRESULT STDMETHODCALLTYPE SetValue(LPCWSTR val); + HRESULT STDMETHODCALLTYPE get_IsReadOnly(BOOL *pRetVal); + HRESULT STDMETHODCALLTYPE get_Value(BSTR *pRetVal); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + +#endif // QWINDOWSUIAVALUEPROVIDER_H diff --git a/src/plugins/platforms/windows/uiautomation/uiautomation.pri b/src/plugins/platforms/windows/uiautomation/uiautomation.pri new file mode 100644 index 0000000000..e3071766d9 --- /dev/null +++ b/src/plugins/platforms/windows/uiautomation/uiautomation.pri @@ -0,0 +1,43 @@ +qtHaveModule(windowsuiautomation_support-private): \ + QT += windowsuiautomation_support-private + +SOURCES += \ + $$PWD/qwindowsuiaaccessibility.cpp \ + $$PWD/qwindowsuiaprovidercache.cpp \ + $$PWD/qwindowsuiamainprovider.cpp \ + $$PWD/qwindowsuiabaseprovider.cpp \ + $$PWD/qwindowsuiavalueprovider.cpp \ + $$PWD/qwindowsuiatextprovider.cpp \ + $$PWD/qwindowsuiatextrangeprovider.cpp \ + $$PWD/qwindowsuiatoggleprovider.cpp \ + $$PWD/qwindowsuiaselectionprovider.cpp \ + $$PWD/qwindowsuiaselectionitemprovider.cpp \ + $$PWD/qwindowsuiainvokeprovider.cpp \ + $$PWD/qwindowsuiarangevalueprovider.cpp \ + $$PWD/qwindowsuiatableprovider.cpp \ + $$PWD/qwindowsuiatableitemprovider.cpp \ + $$PWD/qwindowsuiagridprovider.cpp \ + $$PWD/qwindowsuiagriditemprovider.cpp \ + $$PWD/qwindowsuiautils.cpp + +HEADERS += \ + $$PWD/qwindowsuiaaccessibility.h \ + $$PWD/qwindowsuiaprovidercache.h \ + $$PWD/qwindowsuiamainprovider.h \ + $$PWD/qwindowsuiabaseprovider.h \ + $$PWD/qwindowsuiavalueprovider.h \ + $$PWD/qwindowsuiatextprovider.h \ + $$PWD/qwindowsuiatextrangeprovider.h \ + $$PWD/qwindowsuiatoggleprovider.h \ + $$PWD/qwindowsuiaselectionprovider.h \ + $$PWD/qwindowsuiaselectionitemprovider.h \ + $$PWD/qwindowsuiainvokeprovider.h \ + $$PWD/qwindowsuiarangevalueprovider.h \ + $$PWD/qwindowsuiatableprovider.h \ + $$PWD/qwindowsuiatableitemprovider.h \ + $$PWD/qwindowsuiagridprovider.h \ + $$PWD/qwindowsuiagriditemprovider.h \ + $$PWD/qwindowsuiautils.h + +mingw: LIBS *= -luuid + diff --git a/src/plugins/platforms/windows/windows.pri b/src/plugins/platforms/windows/windows.pri index 6d01d05fcc..f4c396f7c5 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,16 +42,18 @@ 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 \ $$PWD/qwindowsopengltester.h \ - $$PWD/qwindowsthreadpoolrunner.h + $$PWD/qwindowsthreadpoolrunner.h \ $$PWD/qwin10helpers.h INCLUDEPATH += $$PWD @@ -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 @@ -94,7 +109,7 @@ qtConfig(imageformat_png):RESOURCES += $$PWD/cursors.qrc RESOURCES += $$PWD/openglblacklists.qrc -qtConfig(accessibility): include($$PWD/accessible/accessible.pri) +qtConfig(accessibility): include($$PWD/uiautomation/uiautomation.pri) qtConfig(combined-angle-lib) { DEFINES *= LIBEGL_NAME=$${LIBQTANGLE_NAME} 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/qwinrtclipboard.cpp b/src/plugins/platforms/winrt/qwinrtclipboard.cpp index 117cb515df..05c34b82f8 100644 --- a/src/plugins/platforms/winrt/qwinrtclipboard.cpp +++ b/src/plugins/platforms/winrt/qwinrtclipboard.cpp @@ -59,7 +59,7 @@ typedef IEventHandler<IInspectable *> ContentChangedHandler; QT_BEGIN_NAMESPACE QWinRTClipboard::QWinRTClipboard() - : m_mimeData(Q_NULLPTR) + : m_mimeData(nullptr) { QEventDispatcherWinRT::runOnXamlThread([this]() { HRESULT hr; diff --git a/src/plugins/platforms/winrt/qwinrtcursor.cpp b/src/plugins/platforms/winrt/qwinrtcursor.cpp index 28a5f73e6e..3c918df935 100644 --- a/src/plugins/platforms/winrt/qwinrtcursor.cpp +++ b/src/plugins/platforms/winrt/qwinrtcursor.cpp @@ -89,7 +89,7 @@ void QWinRTCursor::changeCursor(QCursor *windowCursor, QWindow *window) switch (windowCursor ? windowCursor->shape() : Qt::ArrowCursor) { case Qt::BlankCursor: hr = QEventDispatcherWinRT::runOnXamlThread([coreWindow]() { - coreWindow->put_PointerCursor(Q_NULLPTR); + coreWindow->put_PointerCursor(nullptr); return S_OK; }); RETURN_VOID_IF_FAILED("Failed to set blank native cursor"); diff --git a/src/plugins/platforms/winrt/qwinrtdrag.cpp b/src/plugins/platforms/winrt/qwinrtdrag.cpp index 15ae024d20..0c918230b3 100644 --- a/src/plugins/platforms/winrt/qwinrtdrag.cpp +++ b/src/plugins/platforms/winrt/qwinrtdrag.cpp @@ -104,7 +104,7 @@ class DragThreadTransferData : public QObject public slots: void handleDrag(); public: - explicit DragThreadTransferData(QObject *parent = Q_NULLPTR); + explicit DragThreadTransferData(QObject *parent = nullptr); QWindow *window; QWinRTInternalMimeData *mime; QPoint point; @@ -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/qwinrtfiledialoghelper.cpp b/src/plugins/platforms/winrt/qwinrtfiledialoghelper.cpp index 62eacba89b..3c90334c8c 100644 --- a/src/plugins/platforms/winrt/qwinrtfiledialoghelper.cpp +++ b/src/plugins/platforms/winrt/qwinrtfiledialoghelper.cpp @@ -83,7 +83,7 @@ public: } HRESULT __stdcall GetView(IVectorView<HSTRING> **view) { - *view = Q_NULLPTR; + *view = nullptr; return E_NOTIMPL; } HRESULT __stdcall IndexOf(HSTRING value, quint32 *index, boolean *found) diff --git a/src/plugins/platforms/winrt/qwinrtfileengine.cpp b/src/plugins/platforms/winrt/qwinrtfileengine.cpp index 58375d331c..76efdf6cc8 100644 --- a/src/plugins/platforms/winrt/qwinrtfileengine.cpp +++ b/src/plugins/platforms/winrt/qwinrtfileengine.cpp @@ -144,7 +144,7 @@ QAbstractFileEngine *QWinRTFileEngineHandler::create(const QString &fileName) co if (file != d->files.end()) return new QWinRTFileEngine(fileName, file.value().Get()); - return Q_NULLPTR; + return nullptr; } static HRESULT getDestinationFolder(const QString &fileName, const QString &newFileName, @@ -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/qwinrtscreen.cpp b/src/plugins/platforms/winrt/qwinrtscreen.cpp index c370c2ec50..e37aeb0bc5 100644 --- a/src/plugins/platforms/winrt/qwinrtscreen.cpp +++ b/src/plugins/platforms/winrt/qwinrtscreen.cpp @@ -47,6 +47,7 @@ #endif #include "qwinrtwindow.h" #include <private/qeventdispatcher_winrt_p.h> +#include <private/qhighdpiscaling_p.h> #include <QtCore/QLoggingCategory> #include <QtGui/QSurfaceFormat> @@ -487,7 +488,8 @@ public: #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) QAtomicPointer<QWinRTWindow> mouseGrabWindow; QAtomicPointer<QWinRTWindow> keyboardGrabWindow; - QWindow *currentPressWindow = 0; + QWindow *currentPressWindow = nullptr; + QWindow *currentTargetWindow = nullptr; }; // To be called from the XAML thread @@ -497,7 +499,7 @@ QWinRTScreen::QWinRTScreen() Q_D(QWinRTScreen); qCDebug(lcQpaWindows) << __FUNCTION__; d->orientation = Qt::PrimaryOrientation; - d->touchDevice = Q_NULLPTR; + d->touchDevice = nullptr; HRESULT hr; ComPtr<Xaml::IWindowStatics> windowStatics; @@ -540,7 +542,7 @@ QWinRTScreen::QWinRTScreen() Q_ASSERT_SUCCEEDED(hr); d->nativeOrientation = static_cast<Qt::ScreenOrientation>(static_cast<int>(qtOrientationsFromNative(displayOrientation))); // Set initial pixel density - onDpiChanged(Q_NULLPTR, Q_NULLPTR); + onDpiChanged(nullptr, nullptr); d->orientation = d->nativeOrientation; ComPtr<IApplicationViewStatics2> applicationViewStatics; @@ -764,7 +766,7 @@ void QWinRTScreen::initialize() Q_ASSERT_SUCCEEDED(hr); hr = d->displayInformation->add_DpiChanged(Callback<DisplayInformationHandler>(this, &QWinRTScreen::onDpiChanged).Get(), &d->displayTokens[&IDisplayInformation::remove_DpiChanged]); Q_ASSERT_SUCCEEDED(hr); - onOrientationChanged(Q_NULLPTR, Q_NULLPTR); + onOrientationChanged(nullptr, nullptr); onVisibilityChanged(nullptr, nullptr); hr = d->redirect->add_PointerRoutedReleased(Callback<RedirectHandler>(this, &QWinRTScreen::onRedirectReleased).Get(), &d->redirectTokens[&ICorePointerRedirector::remove_PointerRoutedReleased]); @@ -864,7 +866,7 @@ void QWinRTScreen::removeWindow(QWindow *window) const Qt::WindowType type = window->type(); if (wasTopWindow && type != Qt::Popup && type != Qt::ToolTip && type != Qt::Tool) - QWindowSystemInterface::handleWindowActivated(Q_NULLPTR, Qt::OtherFocusReason); + QWindowSystemInterface::handleWindowActivated(nullptr, Qt::OtherFocusReason); handleExpose(); QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); #ifndef QT_NO_DRAGANDDROP @@ -1081,11 +1083,11 @@ HRESULT QWinRTScreen::onPointerEntered(ICoreWindow *, IPointerEventArgs *args) pointerPoint->get_Position(&point); QPoint pos(point.X * d->scaleFactor, point.Y * d->scaleFactor); - QWindow *targetWindow = topWindow(); + d->currentTargetWindow = topWindow(); if (d->mouseGrabWindow) - targetWindow = d->mouseGrabWindow.load()->window(); + d->currentTargetWindow = d->mouseGrabWindow.load()->window(); - QWindowSystemInterface::handleEnterEvent(targetWindow, pos, pos); + QWindowSystemInterface::handleEnterEvent(d->currentTargetWindow, pos, pos); } return S_OK; } @@ -1104,11 +1106,11 @@ HRESULT QWinRTScreen::onPointerExited(ICoreWindow *, IPointerEventArgs *args) d->touchPoints.remove(id); - QWindow *targetWindow = nullptr; if (d->mouseGrabWindow) - targetWindow = d->mouseGrabWindow.load()->window(); + d->currentTargetWindow = d->mouseGrabWindow.load()->window(); - QWindowSystemInterface::handleLeaveEvent(targetWindow); + QWindowSystemInterface::handleLeaveEvent(d->currentTargetWindow); + d->currentTargetWindow = nullptr; return S_OK; } @@ -1126,19 +1128,19 @@ HRESULT QWinRTScreen::onPointerUpdated(ICoreWindow *, IPointerEventArgs *args) // Common traits - point, modifiers, properties Point point; pointerPoint->get_Position(&point); - QPointF pos(point.X * d->scaleFactor, point.Y * d->scaleFactor); + const QPointF pos(point.X * d->scaleFactor, point.Y * d->scaleFactor); QPointF localPos = pos; const QPoint posPoint = pos.toPoint(); - QWindow *windowUnderPointer = windowAt(posPoint); - QWindow *targetWindow = windowUnderPointer; + QWindow *windowUnderPointer = windowAt(QHighDpiScaling::mapPositionFromNative(posPoint, this)); + d->currentTargetWindow = windowUnderPointer; if (d->mouseGrabWindow) - targetWindow = d->mouseGrabWindow.load()->window(); + d->currentTargetWindow = d->mouseGrabWindow.load()->window(); - if (targetWindow) { + if (d->currentTargetWindow) { const QPointF globalPosDelta = pos - posPoint; - localPos = targetWindow->mapFromGlobal(posPoint) + globalPosDelta; + localPos = d->currentTargetWindow->mapFromGlobal(posPoint) + globalPosDelta; } VirtualKeyModifiers modifiers; @@ -1173,7 +1175,7 @@ HRESULT QWinRTScreen::onPointerUpdated(ICoreWindow *, IPointerEventArgs *args) boolean isHorizontal; properties->get_IsHorizontalMouseWheel(&isHorizontal); QPoint angleDelta(isHorizontal ? delta : 0, isHorizontal ? 0 : delta); - QWindowSystemInterface::handleWheelEvent(targetWindow, localPos, pos, QPoint(), angleDelta, mods); + QWindowSystemInterface::handleWheelEvent(d->currentTargetWindow, localPos, pos, QPoint(), angleDelta, mods); break; } @@ -1207,15 +1209,22 @@ HRESULT QWinRTScreen::onPointerUpdated(ICoreWindow *, IPointerEventArgs *args) // menus. if (buttons != Qt::NoButton && d->currentPressWindow == nullptr && !d->mouseGrabWindow) d->currentPressWindow = windowUnderPointer; - if (!isPressed && d->currentPressWindow && d->mouseGrabWindow) { + if (buttons == Qt::NoButton && d->currentPressWindow && d->mouseGrabWindow) { const QPointF globalPosDelta = pos - posPoint; const QPointF localPressPos = d->currentPressWindow->mapFromGlobal(posPoint) + globalPosDelta; QWindowSystemInterface::handleMouseEvent(d->currentPressWindow, localPressPos, pos, buttons, mods); d->currentPressWindow = nullptr; } + // If the mouse button is released outside of a window, targetWindow is 0, but the event + // has to be delivered to the window, that initially received the mouse press. Do not reset + // d->currentTargetWindow though, as it is used (and reset) in onPointerExited. + if (buttons == Qt::NoButton && d->currentPressWindow && !d->currentTargetWindow) { + d->currentTargetWindow = d->currentPressWindow; + d->currentPressWindow = nullptr; + } - QWindowSystemInterface::handleMouseEvent(targetWindow, localPos, pos, buttons, mods); + QWindowSystemInterface::handleMouseEvent(d->currentTargetWindow, localPos, pos, buttons, mods); break; } @@ -1269,7 +1278,7 @@ HRESULT QWinRTScreen::onPointerUpdated(ICoreWindow *, IPointerEventArgs *args) it.value().normalPosition = QPointF(point.X/d->logicalRect.width(), point.Y/d->logicalRect.height()); it.value().pressure = pressure; - QWindowSystemInterface::handleTouchEvent(targetWindow, d->touchDevice, d->touchPoints.values(), mods); + QWindowSystemInterface::handleTouchEvent(d->currentTargetWindow, d->touchDevice, d->touchPoints.values(), mods); // Fall-through for pen to generate tablet event if (pointerDeviceType != PointerDeviceType_Pen) @@ -1288,7 +1297,7 @@ HRESULT QWinRTScreen::onPointerUpdated(ICoreWindow *, IPointerEventArgs *args) float rotation; properties->get_Twist(&rotation); - QWindowSystemInterface::handleTabletEvent(targetWindow, isPressed, pos, pos, 0, + QWindowSystemInterface::handleTabletEvent(d->currentTargetWindow, isPressed, pos, pos, 0, pointerType, pressure, xTilt, yTilt, 0, rotation, 0, id, mods); 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..9604b4bbaa 100644 --- a/src/plugins/platforms/winrt/qwinrtwindow.h +++ b/src/plugins/platforms/winrt/qwinrtwindow.h @@ -68,10 +68,10 @@ 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; + bool setMouseGrabEnabled(bool grab) override; + bool setKeyboardGrabEnabled(bool grab) override; EGLSurface eglSurface() const; void createEglSurface(EGLDisplay display, EGLConfig config); diff --git a/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h b/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h index 926e5e22df..07e983a499 100644 --- a/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h +++ b/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h @@ -69,7 +69,7 @@ public: #endif virtual QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const = 0; - virtual QXcbNativeInterfaceHandler *nativeInterfaceHandler() const { return Q_NULLPTR; } + virtual QXcbNativeInterfaceHandler *nativeInterfaceHandler() const { return nullptr; } }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/gl_integrations/qxcbnativeinterfacehandler.cpp b/src/plugins/platforms/xcb/gl_integrations/qxcbnativeinterfacehandler.cpp index ac992b859d..e18656c6ec 100644 --- a/src/plugins/platforms/xcb/gl_integrations/qxcbnativeinterfacehandler.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/qxcbnativeinterfacehandler.cpp @@ -56,37 +56,37 @@ QXcbNativeInterfaceHandler::~QXcbNativeInterfaceHandler() QPlatformNativeInterface::NativeResourceForIntegrationFunction QXcbNativeInterfaceHandler::nativeResourceFunctionForIntegration(const QByteArray &resource) const { Q_UNUSED(resource); - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForContextFunction QXcbNativeInterfaceHandler::nativeResourceFunctionForContext(const QByteArray &resource) const { Q_UNUSED(resource); - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForScreenFunction QXcbNativeInterfaceHandler::nativeResourceFunctionForScreen(const QByteArray &resource) const { Q_UNUSED(resource); - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForWindowFunction QXcbNativeInterfaceHandler::nativeResourceFunctionForWindow(const QByteArray &resource) const { Q_UNUSED(resource); - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForBackingStoreFunction QXcbNativeInterfaceHandler::nativeResourceFunctionForBackingStore(const QByteArray &resource) const { Q_UNUSED(resource); - return Q_NULLPTR; + return nullptr; } QFunctionPointer QXcbNativeInterfaceHandler::platformFunction(const QByteArray &function) const { Q_UNUSED(function); - return Q_NULLPTR; + return nullptr; } QT_END_NAMESPACE 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..c3ce8d8745 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,36 @@ 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); + if (surface->surface()->surfaceClass() == QSurface::Window) { + QXcbWindow *platformWindow = static_cast<QXcbWindow *>(surface); + // OpenGL context might be bound to a non-gui thread use QueuedConnection to sync + // the window from the platformWindow's thread as QXcbWindow is no QObject, an + // event is sent to QXcbConnection. (this is faster than a metacall) + if (platformWindow->needsSync()) + platformWindow->postSyncWindowRequest(); + } } 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 +90,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..fe18bc24db 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp @@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE QXcbEglIntegration::QXcbEglIntegration() - : m_connection(Q_NULLPTR) + : m_connection(nullptr) , m_egl_display(EGL_NO_DISPLAY) { qCDebug(lcQpaGl) << "Xcb EGL gl-integration created"; @@ -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_egl/qxcbeglnativeinterfacehandler.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglnativeinterfacehandler.cpp index 30f5e3a00d..c0e3f820fe 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglnativeinterfacehandler.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglnativeinterfacehandler.cpp @@ -77,7 +77,7 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QXcbEglNativeInte default: break; } - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForContextFunction QXcbEglNativeInterfaceHandler::nativeResourceFunctionForContext(const QByteArray &resource) const @@ -90,7 +90,7 @@ QPlatformNativeInterface::NativeResourceForContextFunction QXcbEglNativeInterfac default: break; } - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForWindowFunction QXcbEglNativeInterfaceHandler::nativeResourceFunctionForWindow(const QByteArray &resource) const @@ -101,7 +101,7 @@ QPlatformNativeInterface::NativeResourceForWindowFunction QXcbEglNativeInterface default: break; } - return Q_NULLPTR; + return nullptr; } void *QXcbEglNativeInterfaceHandler::eglDisplay() @@ -114,11 +114,11 @@ void *QXcbEglNativeInterfaceHandler::eglDisplay() void *QXcbEglNativeInterfaceHandler::eglDisplayForWindow(QWindow *window) { Q_ASSERT(window); - if (window->supportsOpenGL() && window->handle() == Q_NULLPTR) + if (window->supportsOpenGL() && window->handle() == nullptr) return eglDisplay(); else if (window->supportsOpenGL()) return static_cast<QXcbEglWindow *>(window->handle())->glIntegration()->eglDisplay(); - return Q_NULLPTR; + return nullptr; } void *QXcbEglNativeInterfaceHandler::eglContextForContext(QOpenGLContext *context) diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp index 9c3fd26d49..65beac227c 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp @@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE QXcbEglWindow::QXcbEglWindow(QWindow *window, QXcbEglIntegration *glIntegration) : QXcbWindow(window) , m_glIntegration(glIntegration) - , m_config(Q_NULLPTR) + , m_config(nullptr) , m_surface(EGL_NO_SURFACE) { } 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 fc1806f982..21024385b0 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp @@ -171,7 +171,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) @@ -200,7 +200,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 @@ -308,10 +308,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,11 +325,11 @@ void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share) if (!m_context && m_shareContext) { // re-try without a shared glx context m_shareContext = 0; - m_context = glXCreateContext(m_display, visualInfo, Q_NULLPTR, true); + m_context = glXCreateContext(m_display, visualInfo, nullptr, true); } // 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); } @@ -364,7 +364,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 @@ -474,7 +474,7 @@ static QXcbScreen *screenForPlatformSurface(QPlatformSurface *surface) } else if (surfaceClass == QSurface::Offscreen) { return static_cast<QXcbScreen *>(static_cast<QGLXPbuffer *>(surface)->screen()); } - return Q_NULLPTR; + return nullptr; } QVariant QGLXContext::nativeHandle() const @@ -669,8 +669,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")) { @@ -733,8 +735,7 @@ void QGLXContext::queryDummyContext() bool QGLXContext::supportsThreading() { - if (!m_queriedDummyContext) - queryDummyContext(); + queryDummyContext(); return m_supportsThreading; } @@ -742,9 +743,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[] = { @@ -755,17 +757,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..13f03f8bf3 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxintegration.cpp @@ -87,7 +87,7 @@ QT_BEGIN_NAMESPACE #endif QXcbGlxIntegration::QXcbGlxIntegration() - : m_connection(Q_NULLPTR) + : m_connection(nullptr) , m_glx_first_event(0) { qCDebug(lcQpaGl) << "Xcb GLX gl-integration created"; @@ -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/qxcbglxnativeinterfacehandler.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxnativeinterfacehandler.cpp index 638fdd46b5..e9bb4460ff 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxnativeinterfacehandler.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxnativeinterfacehandler.cpp @@ -72,7 +72,7 @@ QPlatformNativeInterface::NativeResourceForContextFunction QXcbGlxNativeInterfac default: break; } - return Q_NULLPTR; + return nullptr; } void *QXcbGlxNativeInterfaceHandler::glxContextForContext(QOpenGLContext *context) 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..5e406017ca 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 @@ -57,14 +58,29 @@ 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); + return nullptr; + + qCDebug(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; + return nullptr; } const xcb_visualtype_t *xcb_visualtype = scr->visualForId(visualInfo->visualid); XFree(visualInfo); + + qCDebug(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..9b31998620 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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, 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, 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, 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..f3653a9438 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..fe9d1fcde9 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp @@ -0,0 +1,650 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..573a0f28ea --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..8b63e5431d --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp @@ -0,0 +1,2843 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..9b01c0a3fc --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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) override; + bool end() override; + + void updateState(const QPaintEngineState &state) 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) override; + void drawLines(const QLineF *lines, int lineCount) override; + + void drawRects(const QRect *rects, int rectCount) override; + void drawRects(const QRectF *rects, int rectCount) override; + + void drawPoints(const QPoint *points, int pointCount) override; + void drawPoints(const QPointF *points, int pointCount) override; + + void drawEllipse(const QRect &r) override; + void drawEllipse(const QRectF &r) override; + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override; + inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) override + { QPaintEngine::drawPolygon(points, pointCount, mode); } + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override; + void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) override; + void drawPath(const QPainterPath &path) override; + void drawTextItem(const QPointF &p, const QTextItem &textItem) override; + void drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor) override; + + virtual Drawable handle() const; + inline Type type() const 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..86c87e5e30 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp @@ -0,0 +1,2114 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..7392cbfccf --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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 override; + void resize(int width, int height) override; + void fromImage(const QImage &img, Qt::ImageConversionFlags flags) override; + void copy(const QPlatformPixmap *data, const QRect &rect) override; + bool scroll(int dx, int dy, const QRect &rect) override; + int metric(QPaintDevice::PaintDeviceMetric metric) const override; + void fill(const QColor &fillColor) override; + QBitmap mask() const override; + void setMask(const QBitmap &mask) override; + bool hasAlphaChannel() const override; + QPixmap transformed(const QTransform &matrix, Qt::TransformationMode mode) const override; + QImage toImage() const override; + QImage toImage(const QRect &rect) const override; + QPaintEngine *paintEngine() const override; + qreal devicePixelRatio() const override; + void setDevicePixelRatio(qreal scaleFactor) 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()) + : 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..aa8dfa5af0 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..1afa00cfc9 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp @@ -0,0 +1,1500 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..7181a1ecc9 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..57b1882e4b --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp @@ -0,0 +1,321 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qxcbconnection.h" +#include "qcolormap_x11_p.h" +#include "qxcbnativepainting.h" +#include "qt_x11_p.h" + +QT_BEGIN_NAMESPACE + +QXcbX11Data *qt_x11Data = 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 auto input = r.begin(); + 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(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..b00ccfcdff --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..1cf45c96d1 100644 --- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp +++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp @@ -69,6 +69,8 @@ public: QXcbShmImage(QXcbScreen *connection, const QSize &size, uint depth, QImage::Format format); ~QXcbShmImage() { destroy(); } + void flushScrolledRegion(bool clientSideScroll); + bool scroll(const QRegion &area, int dx, int dy); QImage *image() { return &m_qimage; } @@ -86,7 +88,8 @@ private: void destroy(); void ensureGC(xcb_drawable_t dst); - void flushPixmap(const QRegion ®ion); + void shmPutImage(xcb_drawable_t drawable, const QRegion ®ion, const QPoint &offset = QPoint()); + void flushPixmap(const QRegion ®ion, bool fullRegion = false); void setClip(const QRegion ®ion); xcb_shm_segment_info_t m_shm_info; @@ -99,18 +102,26 @@ private: xcb_gcontext_t m_gc; xcb_drawable_t m_gc_drawable; - // When using shared memory this is the region currently shared with the server - QRegion m_dirtyShm; - + // When using shared memory these variables are used only for server-side scrolling. // When not using shared memory, we maintain a server-side pixmap with the backing // store as well as repainted content not yet flushed to the pixmap. We only flush // the regions we need and only when these are marked dirty. This way we can just // do a server-side copy on expose instead of sending the pixels every time xcb_pixmap_t m_xcb_pixmap; QRegion m_pendingFlush; + + // This is the scrolled region which is stored in server-side pixmap + QRegion m_scrolledRegion; + + // When using shared memory this is the region currently shared with the server + QRegion m_dirtyShm; + + // When not using shared memory this is a temporary buffer which is uploaded + // as a pixmap region to server QByteArray m_flushBuffer; bool m_hasAlpha; + bool m_clientSideScroll; }; class QXcbShmGraphicsBuffer : public QPlatformGraphicsBuffer @@ -145,13 +156,12 @@ private: QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QImage::Format format) : QXcbObject(screen->connection()) - , m_graphics_buffer(Q_NULLPTR) + , m_graphics_buffer(nullptr) , m_gc(0) , m_gc_drawable(0) , m_xcb_pixmap(0) + , m_clientSideScroll(false) { - Q_XCB_NOOP(connection()); - const xcb_format_t *fmt = connection()->formatForDepth(depth); Q_ASSERT(fmt); @@ -204,13 +214,59 @@ QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QI m_qimage = QImage( (uchar*) m_xcb_image->data, m_xcb_image->width, m_xcb_image->height, m_xcb_image->stride, format); m_graphics_buffer = new QXcbShmGraphicsBuffer(&m_qimage); - 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)); + m_xcb_pixmap = xcb_generate_id(xcb_connection()); + xcb_create_pixmap(xcb_connection(), + m_xcb_image->depth, + m_xcb_pixmap, + screen->screen()->root, + m_xcb_image->width, m_xcb_image->height); +} + +void QXcbShmImage::flushScrolledRegion(bool clientSideScroll) +{ + if (m_clientSideScroll == clientSideScroll) + return; + + m_clientSideScroll = clientSideScroll; + + if (m_scrolledRegion.isNull()) + return; + + if (hasShm() && m_dirtyShm.intersects(m_scrolledRegion)) { + connection()->sync(); + m_dirtyShm = QRegion(); + } + + if (m_clientSideScroll) { + // Copy scrolled image region from server-side pixmap to client-side memory + for (const QRect &rect : m_scrolledRegion) { + const int w = rect.width(); + const int h = rect.height(); + + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_image, + xcb_connection(), + m_xcb_image->format, + m_xcb_pixmap, + rect.x(), rect.y(), + w, h, + ~0u); + + if (reply && reply->depth == m_xcb_image->depth) { + const QImage img(xcb_get_image_data(reply.get()), w, h, m_qimage.format()); + + QPainter p(&m_qimage); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawImage(rect.topLeft(), img); + } + } + m_scrolledRegion = QRegion(); + } else { + // Copy scrolled image region from client-side memory to server-side pixmap + ensureGC(m_xcb_pixmap); + if (hasShm()) + shmPutImage(m_xcb_pixmap, m_scrolledRegion); + else + flushPixmap(m_scrolledRegion, true); } } @@ -218,32 +274,45 @@ extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &o bool QXcbShmImage::scroll(const QRegion &area, int dx, int dy) { - if (image()->isNull()) - return false; + const QRect bounds(QPoint(), size()); + const QRegion scrollArea(area & bounds); + const QPoint delta(dx, dy); - if (hasShm()) - preparePaint(area); + if (m_clientSideScroll) { + if (m_qimage.isNull()) + return false; - const QPoint delta(dx, dy); - for (const QRect &rect : area) - qt_scrollRectInImage(*image(), rect, delta); + if (hasShm()) + preparePaint(scrollArea); + + for (const QRect &rect : scrollArea) + qt_scrollRectInImage(m_qimage, rect, delta); + } else { + if (hasShm()) + shmPutImage(m_xcb_pixmap, m_pendingFlush.intersected(scrollArea)); + else + flushPixmap(scrollArea); - if (m_xcb_pixmap) { - flushPixmap(area); ensureGC(m_xcb_pixmap); - const QRect bounds(QPoint(0, 0), size()); - for (const QRect &src : area) { + + for (const QRect &src : scrollArea) { 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()); } } + m_scrolledRegion |= scrollArea.translated(delta).intersected(bounds); + if (hasShm()) { + m_pendingFlush -= scrollArea; + m_pendingFlush -= m_scrolledRegion; + } + return true; } @@ -251,7 +320,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,27 +334,25 @@ 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; + m_graphics_buffer = nullptr; - if (m_xcb_pixmap) { - Q_XCB_CALL(xcb_free_pixmap(xcb_connection(), m_xcb_pixmap)); - m_xcb_pixmap = 0; - } + xcb_free_pixmap(xcb_connection(), m_xcb_pixmap); + m_xcb_pixmap = 0; } 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; } @@ -355,10 +422,35 @@ static inline quint32 round_up_scanline(quint32 base, quint32 pad) return (base + pad - 1) & -pad; } -void QXcbShmImage::flushPixmap(const QRegion ®ion) +void QXcbShmImage::shmPutImage(xcb_drawable_t drawable, const QRegion ®ion, const QPoint &offset) { - const QVector<QRect> rects = m_pendingFlush.intersected(region).rects(); - m_pendingFlush -= region; + for (const QRect &rect : region) { + const QPoint source = rect.translated(offset).topLeft(); + xcb_shm_put_image(xcb_connection(), + drawable, + m_gc, + m_xcb_image->width, + m_xcb_image->height, + source.x(), source.y(), + rect.width(), rect.height(), + rect.x(), rect.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); +} + +void QXcbShmImage::flushPixmap(const QRegion ®ion, bool fullRegion) +{ + if (!fullRegion) { + auto actualRegion = m_pendingFlush.intersected(region); + m_pendingFlush -= region; + flushPixmap(actualRegion, true); + return; + } xcb_image_t xcb_subimage; memset(&xcb_subimage, 0, sizeof(xcb_image_t)); @@ -374,7 +466,7 @@ void QXcbShmImage::flushPixmap(const QRegion ®ion) const bool needsByteSwap = xcb_subimage.byte_order != m_xcb_image->byte_order; - for (const QRect &rect : rects) { + for (const QRect &rect : region) { // We must make sure that each request is not larger than max_req_size. // Each request takes req_size + m_xcb_image->stride * height bytes. static const uint32_t req_size = sizeof(xcb_put_image_request_t); @@ -425,68 +517,56 @@ 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()); - - for (int i = 0; i < qrects.size(); i++) { - xcb_rects[i].x = qrects[i].x(); - xcb_rects[i].y = qrects[i].y(); - xcb_rects[i].width = qrects[i].width(); - 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())); + const auto xcb_rects = qRegionToXcbRectangleList(region); + 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()); + Q_ASSERT(!m_clientSideScroll); ensureGC(dst); setClip(region); - const QRect bounds = region.boundingRect(); - const QPoint target = bounds.topLeft(); - 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)); - m_dirtyShm |= region.translated(offset); + // Copy scrolled area on server-side from pixmap to window + const QRegion scrolledRegion = m_scrolledRegion.translated(-offset); + for (const QRect &rect : scrolledRegion) { + const QPoint source = rect.translated(offset).topLeft(); + xcb_copy_area(xcb_connection(), + m_xcb_pixmap, + dst, + m_gc, + source.x(), source.y(), + rect.x(), rect.y(), + rect.width(), rect.height()); + } + + // Copy non-scrolled image from client-side memory to server-side window + const QRegion notScrolledArea = region - scrolledRegion; + shmPutImage(dst, notScrolledArea, offset); } else { + const QRect bounds = region.boundingRect(); + const QPoint target = bounds.topLeft(); + const QRect source = bounds.translated(offset); 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) @@ -497,9 +577,9 @@ void QXcbShmImage::preparePaint(const QRegion ®ion) connection()->sync(); m_dirtyShm = QRegion(); } - } else { - m_pendingFlush |= region; } + m_scrolledRegion -= region; + m_pendingFlush |= region; } QXcbBackingStore::QXcbBackingStore(QWindow *window) @@ -573,7 +653,7 @@ QImage QXcbBackingStore::toImage() const QPlatformGraphicsBuffer *QXcbBackingStore::graphicsBuffer() const { - return m_image ? m_image->graphicsBuffer() : Q_NULLPTR; + return m_image ? m_image->graphicsBuffer() : nullptr; } void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) @@ -581,6 +661,8 @@ void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoin if (!m_image || m_image->size().isEmpty()) return; + m_image->flushScrolledRegion(false); + QSize imageSize = m_image->size(); QRegion clipped = region; @@ -592,8 +674,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 +682,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 +690,15 @@ 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); + if (!m_image || m_image->size().isEmpty()) + return; + + m_image->flushScrolledRegion(true); - Q_XCB_NOOP(connection()); + QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground); QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); if (platformWindow->needsSync()) { @@ -632,7 +713,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 +729,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..b091928e8c 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 @@ -911,10 +899,7 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t win, int connection()->flush(); // sleep 50 ms, so we don't use up CPU cycles all the time. - struct timeval usleep_tv; - usleep_tv.tv_sec = 0; - usleep_tv.tv_usec = 50000; - select(0, 0, 0, 0, &usleep_tv); + QThread::msleep(50); } while (timer.elapsed() < timeout); return 0; diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index d36a14b920..c5eae20266 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; @@ -367,7 +369,7 @@ void QXcbConnection::destroyScreen(QXcbScreen *screen) // If there are no other screens on the same virtual desktop, // then transform the physical screen into a fake screen. const QString nameWas = screen->name(); - screen->setOutput(XCB_NONE, Q_NULLPTR); + screen->setOutput(XCB_NONE, nullptr); qCDebug(lcQpaScreen) << "transformed" << nameWas << "to fake" << screen; } else { // There is more than one screen on the same virtual desktop, remove the screen @@ -393,7 +395,7 @@ void QXcbConnection::initializeScreens() { xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup); int xcbScreenNumber = 0; // screen number in the xcb sense - QXcbScreen *primaryScreen = Q_NULLPTR; + QXcbScreen *primaryScreen = nullptr; while (it.rem) { // Each "screen" in xcb terminology is a virtual desktop, // potentially a collection of separate juxtaposed monitors. @@ -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()); + xcb_randr_output_t *outputs = nullptr; + int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources_current.get()); if (outputCount) { timestamp = resources_current->config_timestamp; - outputs = xcb_randr_get_screen_resources_current_outputs(resources_current.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,28 +481,24 @@ 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, - XCB_NONE, Q_NULLPTR, + XCB_NONE, nullptr, screen_info, it.index); siblings << screen; m_screens << screen; xcb_xinerama_screen_info_next(&it); } - free(screens); } } if (siblings.isEmpty()) { // If there are no XRandR outputs or XRandR extension is missing, // then create a fake/legacy screen. - QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, Q_NULLPTR); + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, nullptr); qCDebug(lcQpaScreen) << "created fake screen" << screen; m_screens << screen; if (m_primaryScreenNumber == xcbScreenNumber) { @@ -646,7 +631,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra if (m_glIntegration && !m_glIntegration->initialize(this)) { qCDebug(lcQpaGl) << "Failed to initialize xcb gl-integration" << glIntegrationNames.at(i); delete m_glIntegration; - m_glIntegration = Q_NULLPTR; + m_glIntegration = nullptr; } } if (!m_glIntegration) @@ -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(); @@ -709,7 +689,7 @@ QXcbScreen *QXcbConnection::primaryScreen() const return m_screens.first(); } - return Q_NULLPTR; + return nullptr; } void QXcbConnection::addWindowEventListener(xcb_window_t id, QXcbWindowEventListener *eventListener) @@ -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) @@ -1020,6 +983,12 @@ static Qt::MouseButtons translateMouseButtons(int s) return ret; } +void QXcbConnection::setButtonState(Qt::MouseButton button, bool down) +{ + m_buttonState.setFlag(button, down); + m_button = button; +} + Qt::MouseButton QXcbConnection::translateMouseButton(xcb_button_t s) { switch (s) { @@ -1075,17 +1044,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 +1055,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); + setButtonState(translateMouseButton(ev->detail), true); 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); + setButtonState(translateMouseButton(ev->detail), false); 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 +1097,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 +1169,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 +1180,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 +1232,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 +1243,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) { @@ -1373,7 +1419,7 @@ void QXcbConnection::setFocusWindow(QWindow *w) void QXcbConnection::setMouseGrabber(QXcbWindow *w) { m_mouseGrabber = w; - m_mousePressWindow = Q_NULLPTR; + m_mousePressWindow = nullptr; } void QXcbConnection::setMousePressWindow(QXcbWindow *w) { @@ -1400,10 +1446,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 +1458,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 +1517,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 +1527,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 +1552,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 +1614,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 +1670,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 +1778,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 +2047,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 +2055,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(); } @@ -2042,35 +2076,32 @@ const xcb_format_t *QXcbConnection::formatForDepth(uint8_t depth) const xcb_format_next(&iterator); } - return 0; + qWarning() << "XCB failed to find an xcb_format_t for depth:" << depth; + return nullptr; } 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 +2111,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 +2130,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 +2159,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 +2170,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 +2191,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 +2203,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 +2241,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..07df963ec5 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 @@ -406,6 +409,15 @@ public: const xcb_setup_t *setup() const { return m_setup; } const xcb_format_t *formatForDepth(uint8_t depth) const; + bool imageNeedsEndianSwap() const + { +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + return m_setup->image_byte_order != XCB_IMAGE_ORDER_MSB_FIRST; +#else + return m_setup->image_byte_order != XCB_IMAGE_ORDER_LSB_FIRST; +#endif + } + QXcbKeyboard *keyboard() const { return m_keyboard; } #ifndef QT_NO_CLIPBOARD @@ -426,26 +438,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 +456,38 @@ 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); + Qt::MouseButtons buttonState() const { return m_buttonState; } + Qt::MouseButton button() const { return m_button; } Qt::MouseButton translateMouseButton(xcb_button_t s); QXcbWindow *focusWindow() const { return m_focusWindow; } @@ -501,27 +510,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 startSystemMoveResizeForTouchBegin(xcb_window_t window, const QPoint &point, int corner); + 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 +564,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 +631,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 +672,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; + struct StartSystemMoveResizeInfo { + xcb_window_t window = XCB_NONE; uint16_t deviceid; uint32_t pointid; - Qt::Corner corner; - } m_startSystemResizeInfo; + int corner; + } m_startSystemMoveResizeInfo; #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 +692,16 @@ 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; + Qt::MouseButton m_button = Qt::NoButton; QXcbWindow *m_focusWindow = nullptr; QXcbWindow *m_mouseGrabber = nullptr; @@ -691,9 +712,14 @@ private: QXcbSystemTrayTracker *m_systemTrayTracker = nullptr; QXcbGlIntegration *m_glIntegration = nullptr; bool m_xiGrab = false; + QVector<int> m_xiMasterPointerIds; 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 +729,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 +756,22 @@ private: QXcbConnection *m_connection; }; +#define Q_XCB_REPLY_CONNECTION_ARG(connection, ...) connection + +struct QStdFreeDeleter { + void operator()(void *p) const Q_DECL_NOTHROW { return std::free(p); } +}; + +#define Q_XCB_REPLY(call, ...) \ + std::unique_ptr<call##_reply_t, QStdFreeDeleter>( \ + call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call(__VA_ARGS__), nullptr) \ + ) + +#define Q_XCB_REPLY_UNCHECKED(call, ...) \ + std::unique_ptr<call##_reply_t, QStdFreeDeleter>( \ + call##_reply(Q_XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call##_unchecked(__VA_ARGS__), nullptr) \ + ) + template <typename T> union q_padded_xcb_event { T event; @@ -746,30 +785,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 94f543fd39..39d2857212 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -50,50 +50,38 @@ #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 - if (qEnvironmentVariableIsSet("QT_XCB_DEBUG_XINPUT")) - 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 +94,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 +102,445 @@ 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); + m_xiMasterPointerIds.clear(); + for (int i = 0; i < deviceCount; ++i) { + XIDeviceInfo deviceInfo = devices[i]; + if (deviceInfo.use == XIMasterPointer) { + m_xiMasterPointerIds.append(deviceInfo.deviceid); + continue; + } + if (deviceInfo.use == XISlavePointer) // only slave pointer devices are relevant here + xi2SetupDevice(&deviceInfo, false); } + if (m_xiMasterPointerIds.size() > 1) + qCDebug(lcQpaXInputDevices) << "multi-pointer X detected"; + 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) { @@ -723,15 +784,15 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo } if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen && - xiDeviceEvent->event == m_startSystemResizeInfo.window && - xiDeviceEvent->sourceid == m_startSystemResizeInfo.deviceid && - xiDeviceEvent->detail == m_startSystemResizeInfo.pointid) { - QXcbWindow *window = platformWindowFromId(m_startSystemResizeInfo.window); + xiDeviceEvent->event == m_startSystemMoveResizeInfo.window && + xiDeviceEvent->sourceid == m_startSystemMoveResizeInfo.deviceid && + xiDeviceEvent->detail == m_startSystemMoveResizeInfo.pointid) { + QXcbWindow *window = platformWindowFromId(m_startSystemMoveResizeInfo.window); if (window) { XIAllowTouchEvents(static_cast<Display *>(m_xlib_display), xiDeviceEvent->deviceid, xiDeviceEvent->detail, xiDeviceEvent->event, XIRejectTouch); - window->doStartSystemResize(QPoint(x, y), m_startSystemResizeInfo.corner); - m_startSystemResizeInfo.window = XCB_NONE; + window->doStartSystemMoveResize(QPoint(x, y), m_startSystemMoveResizeInfo.corner); + m_startSystemMoveResizeInfo.window = XCB_NONE; } } break; @@ -764,19 +825,19 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo touchPoint.state = Qt::TouchPointStationary; } -bool QXcbConnection::startSystemResizeForTouchBegin(xcb_window_t window, const QPoint &point, Qt::Corner corner) +bool QXcbConnection::startSystemMoveResizeForTouchBegin(xcb_window_t window, const QPoint &point, int 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(); - m_startSystemResizeInfo.pointid = pointIt.key(); - m_startSystemResizeInfo.corner = corner; + m_startSystemMoveResizeInfo.window = window; + m_startSystemMoveResizeInfo.deviceid = devIt.key(); + m_startSystemMoveResizeInfo.pointid = pointIt.key(); + m_startSystemMoveResizeInfo.corner = corner; return true; } } @@ -784,119 +845,123 @@ bool QXcbConnection::startSystemResizeForTouchBegin(xcb_window_t window, const Q } return false; } +#endif // XCB_USE_XINPUT22 bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) { - if (grab && !canGrab()) - return false; - - int num_devices = 0; Display *xDisplay = static_cast<Display *>(xlib_display()); - XIDeviceInfo *info = XIQueryDevice(xDisplay, XIAllMasterDevices, &num_devices); - if (!info) - return false; - - XIEventMask evmask; - unsigned char mask[XIMaskLen(XI_LASTEVENT)]; - evmask.mask = mask; - evmask.mask_len = sizeof(mask); - memset(mask, 0, sizeof(mask)); - evmask.deviceid = XIAllMasterDevices; - - XISetMask(mask, XI_ButtonPress); - XISetMask(mask, XI_ButtonRelease); - XISetMask(mask, XI_Motion); - XISetMask(mask, XI_Enter); - XISetMask(mask, XI_Leave); - XISetMask(mask, XI_TouchBegin); - XISetMask(mask, XI_TouchUpdate); - XISetMask(mask, XI_TouchEnd); - - bool grabbed = true; - for (int i = 0; i < num_devices; i++) { - int id = info[i].deviceid, n = 0; - XIDeviceInfo *deviceInfo = XIQueryDevice(xDisplay, id, &n); - if (deviceInfo) { - const bool grabbable = deviceInfo->use != XIMasterKeyboard; - XIFreeDeviceInfo(deviceInfo); - if (!grabbable) - continue; - } - if (!grab) { - Status result = XIUngrabDevice(xDisplay, id, CurrentTime); + bool ok = false; + + if (grab) { // grab + XIEventMask evmask; + unsigned char mask[XIMaskLen(XI_LASTEVENT)]; + evmask.mask = mask; + evmask.mask_len = sizeof(mask); + memset(mask, 0, sizeof(mask)); + XISetMask(mask, XI_ButtonPress); + XISetMask(mask, XI_ButtonRelease); + XISetMask(mask, XI_Motion); + XISetMask(mask, XI_Enter); + XISetMask(mask, XI_Leave); + XISetMask(mask, XI_TouchBegin); + XISetMask(mask, XI_TouchUpdate); + XISetMask(mask, XI_TouchEnd); + + for (int id : m_xiMasterPointerIds) { + evmask.deviceid = id; + Status result = XIGrabDevice(xDisplay, id, w, CurrentTime, None, + XIGrabModeAsync, XIGrabModeAsync, False, &evmask); if (result != Success) { - grabbed = false; - qCDebug(lcQpaXInput, "XInput 2.2: failed to ungrab events for device %d (result %d)", id, result); - } - } else { - Status result = XIGrabDevice(xDisplay, id, w, CurrentTime, None, XIGrabModeAsync, - XIGrabModeAsync, False, &evmask); - if (result != Success) { - grabbed = false; - qCDebug(lcQpaXInput, "XInput 2.2: failed to grab events for device %d on window %x (result %d)", id, w, result); + qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x" + "(result %d)", id, w, result); + } else { + // Managed to grab at least one of master pointers, that should be enough + // to properly dismiss windows that rely on mouse grabbing. + ok = true; } } + } else { // ungrab + for (int id : m_xiMasterPointerIds) { + Status result = XIUngrabDevice(xDisplay, id, CurrentTime); + if (result != Success) + qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (result %d)", id, result); + } + // XIUngrabDevice does not seem to wait for a reply from X server (similar to + // xcb_ungrab_pointer). Ungrabbing won't fail, unless NoSuchExtension error + // has occurred due to a programming error somewhere else in the stack. That + // would mean that things will crash soon anyway. + ok = true; } - XIFreeDeviceInfo(info); + if (ok) + m_xiGrab = grab; - m_xiGrab = grabbed; - - return grabbed; + return ok; } -#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 +974,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) { @@ -1011,10 +1069,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) @@ -1031,15 +1130,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 @@ -1244,7 +1334,7 @@ QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id) if (m_tabletData.at(i).deviceId == id) return &m_tabletData[i]; } - return Q_NULLPTR; + return nullptr; } #endif // QT_CONFIG(tabletevent) diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp index 7c62c2e2b3..34b7d0d236 100644 --- a/src/plugins/platforms/xcb/qxcbcursor.cpp +++ b/src/plugins/platforms/xcb/qxcbcursor.cpp @@ -255,29 +255,29 @@ static const uint8_t * const cursor_bits20[] = { forbidden_bits, forbiddenm_bits }; -static const char * const cursorNames[] = { - "left_ptr", - "up_arrow", - "cross", - "wait", - "ibeam", - "size_ver", - "size_hor", - "size_bdiag", - "size_fdiag", - "size_all", - "blank", - "split_v", - "split_h", - "pointing_hand", - "forbidden", - "whats_this", - "left_ptr_watch", - "openhand", - "closedhand", - "copy", - "move", - "link" +static const std::vector<const char *> cursorNames[] = { + { "left_ptr", "default", "top_left_arrow", "left_arrow" }, + { "up_arrow" }, + { "cross" }, + { "wait", "watch", "0426c94ea35c87780ff01dc239897213" }, + { "ibeam", "text", "xterm" }, + { "size_ver", "ns-resize", "v_double_arrow", "00008160000006810000408080010102" }, + { "size_hor", "ew-resize", "h_double_arrow", "028006030e0e7ebffc7f7070c0600140" }, + { "size_bdiag", "nesw-resize", "50585d75b494802d0151028115016902", "fcf1c3c7cd4491d801f1e1c78f100000" }, + { "size_fdiag", "nwse-resize", "38c5dff7c7b8962045400281044508d2", "c7088f0f3e6c8088236ef8e1e3e70000" }, + { "size_all" }, + { "blank" }, + { "split_v", "row-resize", "sb_v_double_arrow", "2870a09082c103050810ffdffffe0204", "c07385c7190e701020ff7ffffd08103c" }, + { "split_h", "col-resize", "sb_h_double_arrow", "043a9f68147c53184671403ffa811cc5", "14fef782d02440884392942c11205230" }, + { "pointing_hand", "pointer", "hand1", "e29285e634086352946a0e7090d73106" }, + { "forbidden", "not-allowed", "crossed_circle", "circle", "03b6e0fcb3499374a867c041f52298f0" }, + { "whats_this", "help", "question_arrow", "5c6cd98b3f3ebcb1f9c7f1c204630408", "d9ce0ab605698f320427677b458ad60b" }, + { "left_ptr_watch", "half-busy", "progress", "00000000000000020006000e7e9ffc3f", "08e8e1c95fe2fc01f976f1e063a24ccd" }, + { "openhand", "fleur", "5aca4d189052212118709018842178c0", "9d800788f1b08800ae810202380a0822" }, + { "closedhand", "grabbing", "208530c400c041818281048008011002" }, + { "dnd-copy", "copy" }, + { "dnd-move", "move" }, + { "dnd-link", "link" } }; QXcbCursorCacheKey::QXcbCursorCacheKey(const QCursor &c) @@ -535,22 +535,13 @@ static xcb_cursor_t loadCursor(void *dpy, int cshape) xcb_cursor_t cursor = XCB_NONE; if (!ptrXcursorLibraryLoadCursor || !dpy) return cursor; - switch (cshape) { - case Qt::DragCopyCursor: - cursor = ptrXcursorLibraryLoadCursor(dpy, "dnd-copy"); - break; - case Qt::DragMoveCursor: - cursor = ptrXcursorLibraryLoadCursor(dpy, "dnd-move"); - break; - case Qt::DragLinkCursor: - cursor = ptrXcursorLibraryLoadCursor(dpy, "dnd-link"); - break; - default: - break; - } - if (!cursor) { - cursor = ptrXcursorLibraryLoadCursor(dpy, cursorNames[cshape]); + + for (const char *cursorName: cursorNames[cshape]) { + cursor = ptrXcursorLibraryLoadCursor(dpy, cursorName); + if (cursor != XCB_NONE) + break; } + return cursor; } #endif // QT_CONFIG(xcb_xlib) / QT_CONFIG(library) @@ -565,7 +556,6 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) #if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) if (cshape >= 0 && cshape <= Qt::LastCursor) { void *dpy = connection()->xlib_display(); - // special case for non-standard dnd-* cursors cursor = loadCursor(dpy, cshape); if (!cursor && !m_gtkCursorThemeInitialized && m_screen->xSettings()->initialized()) { QByteArray gtkCursorTheme = m_screen->xSettings()->setting("Gtk/CursorThemeName").toByteArray(); @@ -579,7 +569,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; } @@ -598,7 +588,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) } if (cursor && cshape >= 0 && cshape < Qt::LastCursor && connection()->hasXFixes()) { - const char *name = cursorNames[cshape]; + const char *name = cursorNames[cshape].front(); xcb_xfixes_set_cursor_name(conn, cursor, strlen(name), name); } @@ -631,10 +621,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 +637,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 @@ -664,7 +650,7 @@ QPoint QXcbCursor::pos() const void QXcbCursor::setPos(const QPoint &pos) { - QXcbVirtualDesktop *virtualDesktop = Q_NULLPTR; + QXcbVirtualDesktop *virtualDesktop = nullptr; queryPointer(connection(), &virtualDesktop, 0); xcb_warp_pointer(xcb_connection(), XCB_NONE, virtualDesktop->root(), 0, 0, 0, 0, pos.x(), pos.y()); xcb_flush(xcb_connection()); diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index 51d09d8927..8ea0ebf966 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 @@ -221,7 +211,7 @@ void QXcbDrag::startDrag() setScreen(current_virtual_desktop->screens().constFirst()->screen()); initiatorWindow = QGuiApplicationPrivate::currentMouseWindow; QBasicDrag::startDrag(); - if (connection()->mouseGrabber() == Q_NULLPTR) + if (connection()->mouseGrabber() == nullptr) shapedPixmapWindow()->setMouseGrabEnabled(true); } @@ -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; @@ -345,7 +315,7 @@ void QXcbDrag::move(const QPoint &globalPos) if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid()) return; - QXcbVirtualDesktop *virtualDesktop = Q_NULLPTR; + QXcbVirtualDesktop *virtualDesktop = nullptr; QPoint cursorPos; QXcbCursor::queryPointer(connection(), &virtualDesktop, &cursorPos); QXcbScreen *screen = virtualDesktop->screenAt(cursorPos); @@ -354,7 +324,7 @@ void QXcbDrag::move(const QPoint &globalPos) if (virtualDesktop != current_virtual_desktop) { setUseCompositing(virtualDesktop->compositingActive()); recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos); - if (connection()->mouseGrabber() == Q_NULLPTR) + if (connection()->mouseGrabber() == nullptr) shapedPixmapWindow()->setMouseGrabEnabled(true); current_virtual_desktop = virtualDesktop; @@ -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..e18a08755b 100644 --- a/src/plugins/platforms/xcb/qxcbimage.cpp +++ b/src/plugins/platforms/xcb/qxcbimage.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qxcbimage.h" +#include <QtCore/QtEndian> #include <QtGui/QColor> #include <QtGui/private/qimage_p.h> #include <QtGui/private/qdrawhelper_p.h> @@ -52,47 +53,108 @@ extern "C" { #undef template #endif +#include "qxcbconnection.h" +#include "qxcbintegration.h" + +namespace { + +QImage::Format imageFormatForMasks(int depth, int bits_per_pixel, int red_mask, int blue_mask) +{ + if (bits_per_pixel == 32) { + switch (depth) { + case 32: + if (red_mask == 0xff0000 && blue_mask == 0xff) + return QImage::Format_ARGB32_Premultiplied; +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + if (red_mask == 0xff && blue_mask == 0xff0000) + return QImage::Format_RGBA8888_Premultiplied; +#else + if (red_mask == 0xff000000 && blue_mask == 0xff00) + return QImage::Format_RGBA8888_Premultiplied; +#endif + if (red_mask == 0x3ff && blue_mask == 0x3ff00000) + return QImage::Format_A2BGR30_Premultiplied; + if (red_mask == 0x3ff00000 && blue_mask == 0x3ff) + return QImage::Format_A2RGB30_Premultiplied; + break; + case 30: + if (red_mask == 0x3ff && blue_mask == 0x3ff00000) + return QImage::Format_BGR30; + if (blue_mask == 0x3ff && red_mask == 0x3ff00000) + return QImage::Format_RGB30; + break; + case 24: + if (red_mask == 0xff0000 && blue_mask == 0xff) + return QImage::Format_RGB32; +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + if (red_mask == 0xff && blue_mask == 0xff0000) + return QImage::Format_RGBX8888; +#else + if (red_mask == 0xff000000 && blue_mask == 0xff00) + return QImage::Format_RGBX8888; +#endif + break; + } + } else if (bits_per_pixel == 16) { + if (depth == 16 && red_mask == 0xf800 && blue_mask == 0x1f) + return QImage::Format_RGB16; + if (depth == 15 && red_mask == 0x7c00 && blue_mask == 0x1f) + return QImage::Format_RGB555; + } + return QImage::Format_Invalid; +} + +} // namespace + QT_BEGIN_NAMESPACE -QImage::Format qt_xcb_imageFormatForVisual(QXcbConnection *connection, uint8_t depth, - const xcb_visualtype_t *visual) +bool qt_xcb_imageFormatForVisual(QXcbConnection *connection, uint8_t depth, const xcb_visualtype_t *visual, + QImage::Format *imageFormat, bool *needsRgbSwap) { - const xcb_format_t *format = connection->formatForDepth(depth); + Q_ASSERT(connection && visual && imageFormat); - if (!visual || !format) - return QImage::Format_Invalid; - - if (depth == 32 && format->bits_per_pixel == 32 && visual->red_mask == 0xff0000 - && visual->green_mask == 0xff00 && visual->blue_mask == 0xff) - return QImage::Format_ARGB32_Premultiplied; - - if (depth == 30 && format->bits_per_pixel == 32 && visual->red_mask == 0x3ff - && visual->green_mask == 0x0ffc00 && visual->blue_mask == 0x3ff00000) - return QImage::Format_BGR30; - - if (depth == 30 && format->bits_per_pixel == 32 && visual->blue_mask == 0x3ff - && visual->green_mask == 0x0ffc00 && visual->red_mask == 0x3ff00000) - return QImage::Format_RGB30; - - if (depth == 24 && format->bits_per_pixel == 32 && visual->red_mask == 0xff0000 - && visual->green_mask == 0xff00 && visual->blue_mask == 0xff) - return QImage::Format_RGB32; - - if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { - if (depth == 24 && format->bits_per_pixel == 32 && visual->blue_mask == 0xff0000 - && visual->green_mask == 0xff00 && visual->red_mask == 0xff) - return QImage::Format_RGBX8888; - } else { - if (depth == 24 && format->bits_per_pixel == 32 && visual->blue_mask == 0xff00 - && visual->green_mask == 0xff0000 && visual->red_mask == 0xff000000) - return QImage::Format_RGBX8888; + if (needsRgbSwap) + *needsRgbSwap = false; + *imageFormat = QImage::Format_Invalid; + + if (depth == 8) { + if (visual->_class == XCB_VISUAL_CLASS_GRAY_SCALE) { + *imageFormat = QImage::Format_Grayscale8; + return true; + } +#if QT_CONFIG(xcb_native_painting) + if (QXcbIntegration::instance() && QXcbIntegration::instance()->nativePaintingEnabled()) { + *imageFormat = QImage::Format_Indexed8; + return true; + } +#endif + return false; + } + + const xcb_format_t *format = connection->formatForDepth(depth); + if (!format) + return false; + + const bool connectionEndianSwap = connection->imageNeedsEndianSwap(); + // We swap the masks and see if we can recognize it as a host format + const quint32 red_mask = connectionEndianSwap ? qbswap(visual->red_mask) : visual->red_mask; + const quint32 blue_mask = connectionEndianSwap ? qbswap(visual->blue_mask) : visual->blue_mask; + + *imageFormat = imageFormatForMasks(depth, format->bits_per_pixel, red_mask, blue_mask); + if (*imageFormat != QImage::Format_Invalid) + return true; + + if (needsRgbSwap) { + *imageFormat = imageFormatForMasks(depth, format->bits_per_pixel, blue_mask, red_mask); + if (*imageFormat != QImage::Format_Invalid) { + *needsRgbSwap = true; + return true; + } } - if (depth == 16 && format->bits_per_pixel == 16 && visual->red_mask == 0xf800 - && visual->green_mask == 0x7e0 && visual->blue_mask == 0x1f) - return QImage::Format_RGB16; + qWarning("Unsupported screen format: depth: %d, bits_per_pixel: %d, red_mask: %x, blue_mask: %x", depth, format->bits_per_pixel, red_mask, blue_mask); - return QImage::Format_Invalid; + return false; } QPixmap qt_xcb_pixmapFromXPixmap(QXcbConnection *connection, xcb_pixmap_t pixmap, @@ -101,60 +163,25 @@ 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; - QImage::Format format = qt_xcb_imageFormatForVisual(connection, depth, visual); - if (format != QImage::Format_Invalid) { + QImage::Format format; + bool needsRgbSwap; + if (qt_xcb_imageFormatForVisual(connection, depth, visual, &format, &needsRgbSwap)) { uint32_t bytes_per_line = length / height; QImage image(const_cast<uint8_t *>(data), width, height, bytes_per_line, format); - uint8_t image_byte_order = connection->setup()->image_byte_order; - - // we may have to swap the byte order - if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && image_byte_order == XCB_IMAGE_ORDER_MSB_FIRST) - || (QSysInfo::ByteOrder == QSysInfo::BigEndian && image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST)) - { - for (int i=0; i < image.height(); i++) { - switch (format) { - case QImage::Format_RGB16: { - ushort *p = (ushort*)image.scanLine(i); - ushort *end = p + image.width(); - while (p < end) { - *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff); - p++; - } - break; - } - case QImage::Format_RGB32: - case QImage::Format_ARGB32_Premultiplied: - case QImage::Format_RGBX8888: { - 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++; - } - break; - } - default: - Q_ASSERT(false); - } - } - } + + if (needsRgbSwap) + image = std::move(image).rgbSwapped(); // fix-up alpha channel if (format == QImage::Format_RGB32 || format == QImage::Format_RGBX8888) { @@ -176,7 +203,6 @@ QPixmap qt_xcb_pixmapFromXPixmap(QXcbConnection *connection, xcb_pixmap_t pixmap result = QPixmap::fromImage(image.copy()); } - free(image_reply); return result; } @@ -214,22 +240,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 +260,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 +288,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/qxcbimage.h b/src/plugins/platforms/xcb/qxcbimage.h index a9071a45de..d718089ec2 100644 --- a/src/plugins/platforms/xcb/qxcbimage.h +++ b/src/plugins/platforms/xcb/qxcbimage.h @@ -48,8 +48,8 @@ QT_BEGIN_NAMESPACE -QImage::Format qt_xcb_imageFormatForVisual(QXcbConnection *connection, - uint8_t depth, const xcb_visualtype_t *visual); +bool qt_xcb_imageFormatForVisual(QXcbConnection *connection, uint8_t depth, const xcb_visualtype_t *visual, + QImage::Format *imageFormat, bool *needsRgbSwap = nullptr); QPixmap qt_xcb_pixmapFromXPixmap(QXcbConnection *connection, xcb_pixmap_t pixmap, int width, int height, int depth, const xcb_visualtype_t *visual); diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index c9ecdceb0d..471287eb44 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,. @@ -111,7 +121,7 @@ static bool runningUnderDebugger() #endif } -QXcbIntegration *QXcbIntegration::m_instance = Q_NULLPTR; +QXcbIntegration *QXcbIntegration::m_instance = nullptr; QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char **argv) : m_services(new QGenericUnixServices) @@ -166,8 +176,8 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char #if defined(QT_DEBUG) if (!noGrabArg && !doGrabArg && underDebugger) { - qDebug("Qt: gdb: -nograb added to command-line options.\n" - "\t Use the -dograb option to enforce grabbing."); + qCDebug(lcQpaXcb, "Qt: gdb: -nograb added to command-line options.\n" + "\t Use the -dograb option to enforce grabbing."); } #endif m_canGrab = (!underDebugger && !noGrabArg) || (underDebugger && doGrabArg); @@ -200,23 +210,48 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char } m_fontDatabase.reset(new QGenericUnixFontDatabase()); + +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) { + qCDebug(lcQpaXcb, "QXCB USING NATIVE PAINTING"); + qt_xcb_native_x11_info_init(defaultConnection()); + } +#endif } QXcbIntegration::~QXcbIntegration() { qDeleteAll(m_connections); - m_instance = Q_NULLPTR; + m_instance = 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 } } @@ -239,7 +274,7 @@ QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLCont QXcbGlIntegration *glIntegration = screen->connection()->glIntegration(); if (!glIntegration) { qWarning("QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled"); - return Q_NULLPTR; + return nullptr; } return glIntegration->createPlatformOpenGLContext(context); } @@ -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); } @@ -256,7 +296,7 @@ QPlatformOffscreenSurface *QXcbIntegration::createPlatformOffscreenSurface(QOffs QXcbGlIntegration *glIntegration = screen->connection()->glIntegration(); if (!glIntegration) { qWarning("QXcbIntegration: Cannot create platform offscreen surface, neither GLX nor EGL are enabled"); - return Q_NULLPTR; + return nullptr; } return glIntegration->createPlatformOffscreenSurface(surface); } @@ -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..2ed66394c9 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp +++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp @@ -53,24 +53,91 @@ #include <stdio.h> #include <X11/keysym.h> -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) #include <X11/extensions/XI2proto.h> #undef KeyPress #undef KeyRelease #endif +#if QT_CONFIG(xcb_xlib) +#include <X11/Xutil.h> +#undef KeyPress +#undef KeyRelease +#endif + #ifndef XK_ISO_Left_Tab #define XK_ISO_Left_Tab 0xFE20 #endif -#ifndef XK_dead_hook -#define XK_dead_hook 0xFE61 +#ifndef XK_dead_a +#define XK_dead_a 0xFE80 +#endif + +#ifndef XK_dead_A +#define XK_dead_A 0xFE81 +#endif + +#ifndef XK_dead_e +#define XK_dead_e 0xFE82 +#endif + +#ifndef XK_dead_E +#define XK_dead_E 0xFE83 +#endif + +#ifndef XK_dead_i +#define XK_dead_i 0xFE84 +#endif + +#ifndef XK_dead_I +#define XK_dead_I 0xFE85 +#endif + +#ifndef XK_dead_o +#define XK_dead_o 0xFE86 +#endif + +#ifndef XK_dead_O +#define XK_dead_O 0xFE87 +#endif + +#ifndef XK_dead_u +#define XK_dead_u 0xFE88 +#endif + +#ifndef XK_dead_U +#define XK_dead_U 0xFE89 +#endif + +#ifndef XK_dead_small_schwa +#define XK_dead_small_schwa 0xFE8A +#endif + +#ifndef XK_dead_capital_schwa +#define XK_dead_capital_schwa 0xFE8B +#endif + +#ifndef XK_dead_greek +#define XK_dead_greek 0xFE8C +#endif + +#ifndef XK_dead_lowline +#define XK_dead_lowline 0xFE90 #endif -#ifndef XK_dead_horn -#define XK_dead_horn 0xFE62 +#ifndef XK_dead_aboveverticalline +#define XK_dead_aboveverticalline 0xFE91 #endif +#ifndef XK_dead_belowverticalline +#define XK_dead_belowverticalline 0xFE92 +#endif + +#ifndef XK_dead_longsolidusoverlay +#define XK_dead_longsolidusoverlay 0xFE93 +#endif + + #ifndef XK_Codeinput #define XK_Codeinput 0xFF37 #endif @@ -429,6 +496,36 @@ static const unsigned int KeyTbl[] = { XK_dead_belowdot, Qt::Key_Dead_Belowdot, XK_dead_hook, Qt::Key_Dead_Hook, XK_dead_horn, Qt::Key_Dead_Horn, + XK_dead_stroke, Qt::Key_Dead_Stroke, + XK_dead_abovecomma, Qt::Key_Dead_Abovecomma, + XK_dead_abovereversedcomma, Qt::Key_Dead_Abovereversedcomma, + XK_dead_doublegrave, Qt::Key_Dead_Doublegrave, + XK_dead_belowring, Qt::Key_Dead_Belowring, + XK_dead_belowmacron, Qt::Key_Dead_Belowmacron, + XK_dead_belowcircumflex, Qt::Key_Dead_Belowcircumflex, + XK_dead_belowtilde, Qt::Key_Dead_Belowtilde, + XK_dead_belowbreve, Qt::Key_Dead_Belowbreve, + XK_dead_belowdiaeresis, Qt::Key_Dead_Belowdiaeresis, + XK_dead_invertedbreve, Qt::Key_Dead_Invertedbreve, + XK_dead_belowcomma, Qt::Key_Dead_Belowcomma, + XK_dead_currency, Qt::Key_Dead_Currency, + XK_dead_a, Qt::Key_Dead_a, + XK_dead_A, Qt::Key_Dead_A, + XK_dead_e, Qt::Key_Dead_e, + XK_dead_E, Qt::Key_Dead_E, + XK_dead_i, Qt::Key_Dead_i, + XK_dead_I, Qt::Key_Dead_I, + XK_dead_o, Qt::Key_Dead_o, + XK_dead_O, Qt::Key_Dead_O, + XK_dead_u, Qt::Key_Dead_u, + XK_dead_U, Qt::Key_Dead_U, + XK_dead_small_schwa, Qt::Key_Dead_Small_Schwa, + XK_dead_capital_schwa, Qt::Key_Dead_Capital_Schwa, + XK_dead_greek, Qt::Key_Dead_Greek, + XK_dead_lowline, Qt::Key_Dead_Lowline, + XK_dead_aboveverticalline, Qt::Key_Dead_Aboveverticalline, + XK_dead_belowverticalline, Qt::Key_Dead_Belowverticalline, + XK_dead_longsolidusoverlay, Qt::Key_Dead_Longsolidusoverlay, // Special keys from X.org - This include multimedia keys, // wireless/bluetooth/uwb keys, special launcher keys, etc. @@ -612,23 +709,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 +745,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() @@ -685,9 +775,303 @@ void QXcbKeyboard::printKeymapError(const char *error) const "directory contains recent enough contents, to update please see http://cgit.freedesktop.org/xkeyboard-config/ ."); } +#if QT_CONFIG(xcb_xlib) +/* Look at a pair of unshifted and shifted key symbols. + * If the 'unshifted' symbol is uppercase and there is no shifted symbol, + * return the matching lowercase symbol; otherwise return 0. + * The caller can then use the previously 'unshifted' symbol as the new + * 'shifted' (uppercase) symbol and the symbol returned by the function + * as the new 'unshifted' (lowercase) symbol.) */ +static xcb_keysym_t getUnshiftedXKey(xcb_keysym_t unshifted, xcb_keysym_t shifted) +{ + if (shifted != XKB_KEY_NoSymbol) // Has a shifted symbol + return 0; + + KeySym xlower; + KeySym xupper; + /* libxkbcommon >= 0.8.0 will have public API functions providing + * functionality equivalent to XConvertCase(), use these once the + * minimal libxkbcommon version is high enough. After that the + * xcb-xlib dependency can be removed */ + XConvertCase(static_cast<KeySym>(unshifted), &xlower, &xupper); + + if (xlower != xupper // Check if symbol is cased + && unshifted == static_cast<xcb_keysym_t>(xupper)) { // Unshifted must be upper case + return static_cast<xcb_keysym_t>(xlower); + } + return 0; +} + +static QByteArray symbolsGroupString(const xcb_keysym_t *symbols, int count) +{ + // Don't output trailing NoSymbols + while (count > 0 && symbols[count - 1] == XKB_KEY_NoSymbol) + count--; + + QByteArray groupString; + for (int symIndex = 0; symIndex < count; symIndex++) { + xcb_keysym_t sym = symbols[symIndex]; + char symString[64]; + if (sym == XKB_KEY_NoSymbol) + strcpy(symString, "NoSymbol"); + else + xkb_keysym_get_name(sym, symString, sizeof(symString)); + + if (!groupString.isEmpty()) + groupString += ", "; + groupString += symString; + } + return groupString; +} + +struct xkb_keymap *QXcbKeyboard::keymapFromCore() +{ + /* Construct an XKB keymap string from information queried from + * the X server */ + QByteArray keymap; + keymap += "xkb_keymap {\n"; + + const xcb_keycode_t minKeycode = connection()->setup()->min_keycode; + const xcb_keycode_t maxKeycode = connection()->setup()->max_keycode; + + // Generate symbolic names from keycodes + { + keymap += + "xkb_keycodes \"core\" {\n" + "\tminimum = " + QByteArray::number(minKeycode) + ";\n" + "\tmaximum = " + QByteArray::number(maxKeycode) + ";\n"; + for (int code = minKeycode; code <= maxKeycode; code++) { + auto codeStr = QByteArray::number(code); + keymap += "<K" + codeStr + "> = " + codeStr + ";\n"; + } + /* TODO: indicators? + */ + keymap += "};\n"; // xkb_keycodes + } + + /* Set up default types (xkbcommon automatically assigns these to + * symbols, but doesn't have shift info) */ + keymap += + "xkb_types \"core\" {\n" + "virtual_modifiers NumLock,Alt,LevelThree;\n" + "type \"ONE_LEVEL\" {\n" + "modifiers= none;\n" + "level_name[Level1] = \"Any\";\n" + "};\n" + "type \"TWO_LEVEL\" {\n" + "modifiers= Shift;\n" + "map[Shift]= Level2;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "};\n" + "type \"ALPHABETIC\" {\n" + "modifiers= Shift+Lock;\n" + "map[Shift]= Level2;\n" + "map[Lock]= Level2;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Caps\";\n" + "};\n" + "type \"KEYPAD\" {\n" + "modifiers= Shift+NumLock;\n" + "map[Shift]= Level2;\n" + "map[NumLock]= Level2;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Number\";\n" + "};\n" + "type \"FOUR_LEVEL\" {\n" + "modifiers= Shift+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Shift Alt\";\n" + "};\n" + "type \"FOUR_LEVEL_ALPHABETIC\" {\n" + "modifiers= Shift+Lock+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[Lock]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "map[Lock+LevelThree]= Level4;\n" + "map[Shift+Lock+LevelThree]= Level3;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Shift Alt\";\n" + "};\n" + "type \"FOUR_LEVEL_SEMIALPHABETIC\" {\n" + "modifiers= Shift+Lock+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[Lock]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "map[Lock+LevelThree]= Level3;\n" + "preserve[Lock+LevelThree]= Lock;\n" + "map[Shift+Lock+LevelThree]= Level4;\n" + "preserve[Shift+Lock+LevelThree]= Lock;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Shift Alt\";\n" + "};\n" + "type \"FOUR_LEVEL_KEYPAD\" {\n" + "modifiers= Shift+NumLock+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[NumLock]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "map[NumLock+LevelThree]= Level4;\n" + "map[Shift+NumLock+LevelThree]= Level3;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Number\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Alt Number\";\n" + "};\n" + "};\n"; // xkb_types + + // Generate mapping between symbolic names and keysyms + { + QVector<xcb_keysym_t> xkeymap; + int keysymsPerKeycode = 0; + { + int keycodeCount = maxKeycode - minKeycode + 1; + if (auto keymapReply = Q_XCB_REPLY(xcb_get_keyboard_mapping, xcb_connection(), + minKeycode, keycodeCount)) { + keysymsPerKeycode = keymapReply->keysyms_per_keycode; + int numSyms = keycodeCount * keysymsPerKeycode; + auto keymapPtr = xcb_get_keyboard_mapping_keysyms(keymapReply.get()); + xkeymap.resize(numSyms); + for (int i = 0; i < numSyms; i++) + xkeymap[i] = keymapPtr[i]; + } + } + if (xkeymap.isEmpty()) + return nullptr; + + KeysymModifierMap keysymMods(keysymsToModifiers()); + static const char *const builtinModifiers[] = + { "Shift", "Lock", "Control", "Mod1", "Mod2", "Mod3", "Mod4", "Mod5" }; + + /* Level 3 symbols (e.g. AltGr+something) seem to come in two flavors: + * - as a proper level 3 in group 1, at least on recent X.org versions + * - 'disguised' as group 2, on 'legacy' X servers + * In the 2nd case, remap group 2 to level 3, that seems to work better + * in practice */ + bool mapGroup2ToLevel3 = keysymsPerKeycode < 5; + + keymap += "xkb_symbols \"core\" {\n"; + for (int code = minKeycode; code <= maxKeycode; code++) { + auto codeMap = xkeymap.constData() + (code - minKeycode) * keysymsPerKeycode; + + const int maxGroup1 = 4; // We only support 4 shift states anyway + const int maxGroup2 = 2; // Only 3rd and 4th keysym are group 2 + xcb_keysym_t symbolsGroup1[maxGroup1]; + xcb_keysym_t symbolsGroup2[maxGroup2]; + for (int i = 0; i < maxGroup1 + maxGroup2; i++) { + xcb_keysym_t sym = i < keysymsPerKeycode ? codeMap[i] : XKB_KEY_NoSymbol; + if (mapGroup2ToLevel3) { + // Merge into single group + if (i < maxGroup1) + symbolsGroup1[i] = sym; + } else { + // Preserve groups + if (i < 2) + symbolsGroup1[i] = sym; + else if (i < 4) + symbolsGroup2[i - 2] = sym; + else + symbolsGroup1[i - 2] = sym; + } + } + + /* Fix symbols so the unshifted and shifted symbols have + * lower resp. upper case */ + if (auto lowered = getUnshiftedXKey(symbolsGroup1[0], symbolsGroup1[1])) { + symbolsGroup1[1] = symbolsGroup1[0]; + symbolsGroup1[0] = lowered; + } + if (auto lowered = getUnshiftedXKey(symbolsGroup2[0], symbolsGroup2[1])) { + symbolsGroup2[1] = symbolsGroup2[0]; + symbolsGroup2[0] = lowered; + } + + QByteArray groupStr1 = symbolsGroupString(symbolsGroup1, maxGroup1); + if (groupStr1.isEmpty()) + continue; + + keymap += "key <K" + QByteArray::number(code) + "> { "; + keymap += "symbols[Group1] = [ " + groupStr1 + " ]"; + QByteArray groupStr2 = symbolsGroupString(symbolsGroup2, maxGroup2); + if (!groupStr2.isEmpty()) + keymap += ", symbols[Group2] = [ " + groupStr2 + " ]"; + + // See if this key code is for a modifier + xcb_keysym_t modifierSym = XKB_KEY_NoSymbol; + for (int symIndex = 0; symIndex < keysymsPerKeycode; symIndex++) { + xcb_keysym_t sym = codeMap[symIndex]; + + if (sym == XKB_KEY_Alt_L + || sym == XKB_KEY_Meta_L + || sym == XKB_KEY_Mode_switch + || sym == XKB_KEY_Super_L + || sym == XKB_KEY_Super_R + || sym == XKB_KEY_Hyper_L + || sym == XKB_KEY_Hyper_R) { + modifierSym = sym; + break; + } + } + + // AltGr + if (modifierSym == XKB_KEY_Mode_switch) + keymap += ", virtualMods=LevelThree"; + keymap += " };\n"; // key + + // Generate modifier mappings + int modNum = keysymMods.value(modifierSym, -1); + if (modNum != -1) { + // Here modNum is always < 8 (see keysymsToModifiers()) + keymap += QByteArray("modifier_map ") + builtinModifiers[modNum] + + " { <K" + QByteArray::number(code) + "> };\n"; + } + } + // TODO: indicators? + keymap += "};\n"; // xkb_symbols + } + + // We need an "Alt" modifier, provide via the xkb_compatibility section + keymap += + "xkb_compatibility \"core\" {\n" + "virtual_modifiers NumLock,Alt,LevelThree;\n" + "interpret Alt_L+AnyOf(all) {\n" + "virtualModifier= Alt;\n" + "action= SetMods(modifiers=modMapMods,clearLocks);\n" + "};\n" + "interpret Alt_R+AnyOf(all) {\n" + "virtualModifier= Alt;\n" + "action= SetMods(modifiers=modMapMods,clearLocks);\n" + "};\n" + "};\n"; + + /* TODO: There is an issue with modifier state not being handled + * correctly if using Xming with XKEYBOARD disabled. */ + + keymap += "};\n"; // xkb_keymap + + return xkb_keymap_new_from_buffer(xkb_context, + keymap.constData(), + keymap.size(), + XKB_KEYMAP_FORMAT_TEXT_V1, + static_cast<xkb_keymap_compile_flags>(0)); +} +#endif + void QXcbKeyboard::updateKeymap() { m_config = true; + m_keymap_is_core = false; // set xkb context object if (!xkb_context) { if (qEnvironmentVariableIsSet("QT_XKB_CONFIG_ROOT")) { @@ -721,9 +1105,23 @@ void QXcbKeyboard::updateKeymap() } #endif if (!xkb_keymap) { - // Compile a keymap from RMLVO (rules, models, layouts, variants and options) names + // Read xkb RMLVO (rules, models, layouts, variants and options) names readXKBConfig(); - xkb_keymap = xkb_keymap_new_from_names(xkb_context, &xkb_names, (xkb_keymap_compile_flags)0); +#if QT_CONFIG(xcb_xlib) + bool rmlvo_is_incomplete = !xkb_names.rules || !(*xkb_names.rules) + || !xkb_names.model || !(*xkb_names.model) + || !xkb_names.layout || !(*xkb_names.layout); + if (rmlvo_is_incomplete) { + // Try to build xkb map from core mapping information + xkb_keymap = keymapFromCore(); + m_keymap_is_core = xkb_keymap != 0; + } +#endif + if (!xkb_keymap) { + // Compile a keymap from RMLVO + xkb_keymap = xkb_keymap_new_from_names(xkb_context, &xkb_names, + static_cast<xkb_keymap_compile_flags> (0)); + } if (!xkb_keymap) { // last fallback is to used hard-coded keymap name, see DEFAULT_XKB_* in xkbcommon.pri qWarning() << "Qt: Could not determine keyboard configuration data" @@ -807,7 +1205,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()) { @@ -915,9 +1313,11 @@ xkb_keysym_t QXcbKeyboard::lookupLatinKeysym(xkb_keycode_t keycode) const // If user layouts don't contain any layout that results in a latin key, we query a // key from "US" layout, this allows for latin-key-based shorcuts to work even when // users have only one (non-latin) layout set. + // But don't do this if using keymap obtained through the core protocol, as the key + // codes may not match up with those expected by the XKB keymap. xkb_mod_mask_t latchedMods = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_LATCHED); xkb_mod_mask_t lockedMods = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_LOCKED); - if (sym == XKB_KEY_NoSymbol && !m_hasLatinLayout) { + if (sym == XKB_KEY_NoSymbol && !m_hasLatinLayout && !m_keymap_is_core) { if (!latin_keymap) { const struct xkb_rule_names names = { xkb_names.rules, xkb_names.model, "us", 0, 0 }; latin_keymap = xkb_keymap_new_from_names(xkb_context, &names, (xkb_keymap_compile_flags)0); @@ -1124,7 +1524,7 @@ int QXcbKeyboard::keysymToQtKey(xcb_keysym_t keysym, Qt::KeyboardModifiers &modi modifiers |= Qt::KeypadModifier; } else if (text.length() == 1 && text.unicode()->unicode() > 0x1f && text.unicode()->unicode() != 0x7f - && !(keysym >= XK_dead_grave && keysym <= XK_dead_currency)) { + && !(keysym >= XK_dead_grave && keysym <= XK_dead_longsolidusoverlay)) { code = text.unicode()->toUpper().unicode(); } else { // any other keys @@ -1172,23 +1572,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 +1629,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,29 +1692,60 @@ void QXcbKeyboard::updateVModToRModMapping() rmod_masks.hyper = modmap; } - free(map_reply); resolveMaskConflicts(); #endif } +// Small helper: set modifier bit, if modifier position is valid +static inline void applyModifier(uint *mask, int modifierBit) +{ + if (modifierBit >= 0 && modifierBit < 8) + *mask |= 1 << modifierBit; +} + void QXcbKeyboard::updateModifiers() { + memset(&rmod_masks, 0, sizeof(rmod_masks)); + + // Compute X modifier bits for Qt modifiers + KeysymModifierMap keysymMods(keysymsToModifiers()); + applyModifier(&rmod_masks.alt, keysymMods.value(XK_Alt_L, -1)); + applyModifier(&rmod_masks.alt, keysymMods.value(XK_Alt_R, -1)); + applyModifier(&rmod_masks.meta, keysymMods.value(XK_Meta_L, -1)); + applyModifier(&rmod_masks.meta, keysymMods.value(XK_Meta_R, -1)); + applyModifier(&rmod_masks.altgr, keysymMods.value(XK_Mode_switch, -1)); + applyModifier(&rmod_masks.super, keysymMods.value(XK_Super_L, -1)); + applyModifier(&rmod_masks.super, keysymMods.value(XK_Super_R, -1)); + applyModifier(&rmod_masks.hyper, keysymMods.value(XK_Hyper_L, -1)); + applyModifier(&rmod_masks.hyper, keysymMods.value(XK_Hyper_R, -1)); + + resolveMaskConflicts(); +} + +// Small helper: check if an array of xcb_keycode_t contains a certain code +static inline bool keycodes_contains(xcb_keycode_t *codes, xcb_keycode_t which) +{ + while (*codes != XCB_NO_SYMBOL) { + if (*codes == which) return true; + codes++; + } + return false; +} + +QXcbKeyboard::KeysymModifierMap QXcbKeyboard::keysymsToModifiers() +{ // The core protocol does not provide a convenient way to determine the mapping // of modifier bits. Clients must retrieve and search the modifier map to determine // the keycodes bound to each modifier, and then retrieve and search the keyboard // mapping to determine the keysyms bound to the keycodes. They must repeat this // 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) { + KeysymModifierMap map; + + auto modMapReply = Q_XCB_REPLY(xcb_get_modifier_mapping, xcb_connection()); + if (!modMapReply) { qWarning("Qt: failed to get modifier mapping"); - free(error); - return; + return map; } // for Alt and Meta L and R are the same @@ -1338,36 +1760,63 @@ 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); - const int w = modMapReply->keycodes_per_modifier; - for (size_t i = 0; i < numSymbols; ++i) { - for (int bit = 0; bit < 8; ++bit) { - uint mask = 1 << bit; - for (int x = 0; x < w; ++x) { - xcb_keycode_t keyCode = modMap[x + bit * w]; - xcb_keycode_t *itk = modKeyCodes[i]; - while (itk && *itk != XCB_NO_SYMBOL) - if (*itk++ == keyCode) { - uint sym = symbols[i]; - if ((sym == XK_Alt_L || sym == XK_Alt_R)) - rmod_masks.alt = mask; - if ((sym == XK_Meta_L || sym == XK_Meta_R)) - rmod_masks.meta = mask; - if (sym == XK_Mode_switch) - rmod_masks.altgr = mask; - if ((sym == XK_Super_L) || (sym == XK_Super_R)) - rmod_masks.super = mask; - if ((sym == XK_Hyper_L) || (sym == XK_Hyper_R)) - rmod_masks.hyper = mask; - } + xcb_keycode_t *modMap = xcb_get_modifier_mapping_keycodes(modMapReply.get()); + const int modMapLength = xcb_get_modifier_mapping_keycodes_length(modMapReply.get()); + /* For each modifier of "Shift, Lock, Control, Mod1, Mod2, Mod3, + * Mod4, and Mod5" the modifier map contains keycodes_per_modifier + * key codes that are associated with a modifier. + * + * As an example, take this 'xmodmap' output: + * xmodmap: up to 4 keys per modifier, (keycodes in parentheses): + * + * shift Shift_L (0x32), Shift_R (0x3e) + * lock Caps_Lock (0x42) + * control Control_L (0x25), Control_R (0x69) + * mod1 Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd) + * mod2 Num_Lock (0x4d) + * mod3 + * mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) + * mod5 ISO_Level3_Shift (0x5c), Mode_switch (0xcb) + * + * The corresponding raw modifier map would contain keycodes for: + * Shift_L (0x32), Shift_R (0x3e), 0, 0, + * Caps_Lock (0x42), 0, 0, 0, + * Control_L (0x25), Control_R (0x69), 0, 0, + * Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd), 0, + * Num_Lock (0x4d), 0, 0, 0, + * 0,0,0,0, + * Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf), + * ISO_Level3_Shift (0x5c), Mode_switch (0xcb), 0, 0 + */ + + /* Create a map between a modifier keysym (as per the symbols array) + * and the modifier bit it's associated with (if any). + * As modMap contains key codes, search modKeyCodes for a match; + * if one is found we can look up the associated keysym. + * Together with the modifier index this will be used + * to compute a mapping between X modifier bits and Qt's + * modifiers (Alt, Ctrl etc). */ + for (int i = 0; i < modMapLength; i++) { + if (modMap[i] == XCB_NO_SYMBOL) + continue; + // Get key symbol for key code + for (size_t k = 0; k < numSymbols; k++) { + if (modKeyCodes[k] && keycodes_contains(modKeyCodes[k], modMap[i])) { + // Key code is for modifier. Record mapping + xcb_keysym_t sym = symbols[k]; + /* As per modMap layout explanation above, dividing + * by keycodes_per_modifier gives the 'row' in the + * modifier map, which in turn is the modifier bit. */ + map[sym] = i / modMapReply->keycodes_per_modifier; + break; } } } for (size_t i = 0; i < numSymbols; ++i) free(modKeyCodes[i]); - free(modMapReply); - resolveMaskConflicts(); + + return map; } void QXcbKeyboard::resolveMaskConflicts() @@ -1449,8 +1898,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 +1918,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 +1949,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 +1972,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..7ee8e9e90d 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) @@ -94,8 +94,13 @@ protected: void readXKBConfig(); void clearXKBConfig(); +#if QT_CONFIG(xcb_xlib) + struct xkb_keymap *keymapFromCore(); +#endif // when XKEYBOARD not present on the X server void updateModifiers(); + typedef QMap<xcb_keysym_t, int> KeysymModifierMap; + KeysymModifierMap keysymsToModifiers(); // when XKEYBOARD is present on the X server void updateVModMapping(); void updateVModToRModMapping(); @@ -107,6 +112,7 @@ private: void updateXKBStateFromState(struct xkb_state *kb_state, quint16 state); bool m_config = false; + bool m_keymap_is_core = false; xcb_keycode_t m_autorepeat_code = 0; struct xkb_context *xkb_context = nullptr; diff --git a/src/plugins/platforms/xcb/qxcbmime.cpp b/src/plugins/platforms/xcb/qxcbmime.cpp index 2848446098..58e2e8c0e6 100644 --- a/src/plugins/platforms/xcb/qxcbmime.cpp +++ b/src/plugins/platforms/xcb/qxcbmime.cpp @@ -160,9 +160,10 @@ QVector<xcb_atom_t> QXcbMime::mimeAtomsForFormat(QXcbConnection *connection, con return atoms; } -QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, const QByteArray &data, const QString &format, +QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, const QByteArray &d, const QString &format, QVariant::Type requestedType, const QByteArray &encoding) { + QByteArray data = d; QString atomName = mimeAtomToString(connection, a); // qDebug() << "mimeConvertDataToFormat" << format << atomName << data; @@ -182,8 +183,11 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, // special cases for string types if (format == QLatin1String("text/plain")) { - if (a == connection->atom(QXcbAtom::UTF8_STRING)) + if (data.endsWith('\0')) + data.chop(1); + if (a == connection->atom(QXcbAtom::UTF8_STRING)) { return QString::fromUtf8(data); + } if (a == XCB_ATOM_STRING || a == connection->atom(QXcbAtom::TEXT)) return QString::fromLatin1(data); @@ -221,6 +225,9 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, } } } + // 8 byte encoding, remove a possible 0 at the end + if (data.endsWith('\0')) + data.chop(1); } if (atomName == format) diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp index 97dcb8f328..22d90d6ac2 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); @@ -93,7 +101,7 @@ QXcbNativeInterface::QXcbNativeInterface() : static inline QXcbSystemTrayTracker *systemTrayTracker(const QScreen *s) { if (!s) - return Q_NULLPTR; + return nullptr; return static_cast<const QXcbScreen *>(s->handle())->connection()->systemTrayTracker(); } @@ -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() @@ -195,7 +194,7 @@ void *QXcbNativeInterface::nativeResourceForScreen(const QByteArray &resourceStr { if (!screen) { qWarning("nativeResourceForScreen: null screen"); - return Q_NULLPTR; + return nullptr; } QByteArray lowerCaseResource = resourceString.toLower(); @@ -237,7 +236,7 @@ void *QXcbNativeInterface::nativeResourceForScreen(const QByteArray &resourceStr break; case CompositingEnabled: if (QXcbVirtualDesktop *vd = xcbScreen->virtualDesktop()) - result = vd->compositingActive() ? this : Q_NULLPTR; + result = vd->compositingActive() ? this : nullptr; break; default: break; @@ -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; } @@ -287,7 +294,7 @@ void *QXcbNativeInterface::nativeResourceForCursor(const QByteArray &resource, c } } } - return Q_NULLPTR; + return nullptr; } #endif // !QT_NO_CURSOR @@ -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; } @@ -309,7 +323,7 @@ QPlatformNativeInterface::NativeResourceForContextFunction QXcbNativeInterface:: QPlatformNativeInterface::NativeResourceForContextFunction func = handlerNativeResourceFunctionForContext(lowerCaseResource); if (func) return func; - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForScreenFunction QXcbNativeInterface::nativeResourceFunctionForScreen(const QByteArray &resource) @@ -376,13 +390,13 @@ QFunctionPointer QXcbNativeInterface::platformFunction(const QByteArray &functio if (function == QXcbScreenFunctions::virtualDesktopNumberIdentifier()) return QFunctionPointer(QXcbScreenFunctions::VirtualDesktopNumber(QXcbScreen::virtualDesktopNumberStatic)); - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::appTime(const QXcbScreen *screen) { if (!screen) - return Q_NULLPTR; + return nullptr; return reinterpret_cast<void *>(quintptr(screen->connection()->time())); } @@ -390,7 +404,7 @@ void *QXcbNativeInterface::appTime(const QXcbScreen *screen) void *QXcbNativeInterface::appUserTime(const QXcbScreen *screen) { if (!screen) - return Q_NULLPTR; + return nullptr; return reinterpret_cast<void *>(quintptr(screen->connection()->netWmUserTime())); } @@ -398,7 +412,7 @@ void *QXcbNativeInterface::appUserTime(const QXcbScreen *screen) void *QXcbNativeInterface::getTimestamp(const QXcbScreen *screen) { if (!screen) - return Q_NULLPTR; + return nullptr; return reinterpret_cast<void *>(quintptr(screen->connection()->getTimestamp())); } @@ -438,7 +452,7 @@ void *QXcbNativeInterface::display() if (defaultConnection) return defaultConnection->xlib_display(); #endif - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::connection() @@ -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); @@ -500,10 +525,10 @@ QXcbScreen *QXcbNativeInterface::qPlatformScreenForWindow(QWindow *window) QXcbScreen *screen; if (window) { QScreen *qs = window->screen(); - screen = static_cast<QXcbScreen *>(qs ? qs->handle() : Q_NULLPTR); + screen = static_cast<QXcbScreen *>(qs ? qs->handle() : nullptr); } else { QScreen *qs = QGuiApplication::primaryScreen(); - screen = static_cast<QXcbScreen *>(qs ? qs->handle() : Q_NULLPTR); + screen = static_cast<QXcbScreen *>(qs ? qs->handle() : nullptr); } return screen; } @@ -512,23 +537,23 @@ void *QXcbNativeInterface::displayForWindow(QWindow *window) { #if QT_CONFIG(xcb_xlib) QXcbScreen *screen = qPlatformScreenForWindow(window); - return screen ? screen->connection()->xlib_display() : Q_NULLPTR; + return screen ? screen->connection()->xlib_display() : nullptr; #else Q_UNUSED(window); - return Q_NULLPTR; + return nullptr; #endif } void *QXcbNativeInterface::connectionForWindow(QWindow *window) { QXcbScreen *screen = qPlatformScreenForWindow(window); - return screen ? screen->xcb_connection() : Q_NULLPTR; + return screen ? screen->xcb_connection() : nullptr; } void *QXcbNativeInterface::screenForWindow(QWindow *window) { QXcbScreen *screen = qPlatformScreenForWindow(window); - return screen ? screen->screen() : Q_NULLPTR; + return screen ? screen->screen() : nullptr; } void QXcbNativeInterface::addHandler(QXcbNativeInterfaceHandler *handler) @@ -550,7 +575,7 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QXcbNativeInterfa if (result) return result; } - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForContextFunction QXcbNativeInterface::handlerNativeResourceFunctionForContext(const QByteArray &resource) const @@ -561,7 +586,7 @@ QPlatformNativeInterface::NativeResourceForContextFunction QXcbNativeInterface:: if (result) return result; } - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForScreenFunction QXcbNativeInterface::handlerNativeResourceFunctionForScreen(const QByteArray &resource) const @@ -572,7 +597,7 @@ QPlatformNativeInterface::NativeResourceForScreenFunction QXcbNativeInterface::h if (result) return result; } - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForWindowFunction QXcbNativeInterface::handlerNativeResourceFunctionForWindow(const QByteArray &resource) const @@ -583,7 +608,7 @@ QPlatformNativeInterface::NativeResourceForWindowFunction QXcbNativeInterface::h if (result) return result; } - return Q_NULLPTR; + return nullptr; } QPlatformNativeInterface::NativeResourceForBackingStoreFunction QXcbNativeInterface::handlerNativeResourceFunctionForBackingStore(const QByteArray &resource) const @@ -594,7 +619,7 @@ QPlatformNativeInterface::NativeResourceForBackingStoreFunction QXcbNativeInterf if (result) return result; } - return Q_NULLPTR; + return nullptr; } QFunctionPointer QXcbNativeInterface::handlerPlatformFunction(const QByteArray &function) const @@ -605,7 +630,7 @@ QFunctionPointer QXcbNativeInterface::handlerPlatformFunction(const QByteArray & if (func) return func; } - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::handlerNativeResourceForIntegration(const QByteArray &resource) const @@ -613,7 +638,7 @@ void *QXcbNativeInterface::handlerNativeResourceForIntegration(const QByteArray NativeResourceForIntegrationFunction func = handlerNativeResourceFunctionForIntegration(resource); if (func) return func(); - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::handlerNativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) const @@ -621,7 +646,7 @@ void *QXcbNativeInterface::handlerNativeResourceForContext(const QByteArray &res NativeResourceForContextFunction func = handlerNativeResourceFunctionForContext(resource); if (func) return func(context); - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::handlerNativeResourceForScreen(const QByteArray &resource, QScreen *screen) const @@ -629,7 +654,7 @@ void *QXcbNativeInterface::handlerNativeResourceForScreen(const QByteArray &reso NativeResourceForScreenFunction func = handlerNativeResourceFunctionForScreen(resource); if (func) return func(screen); - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::handlerNativeResourceForWindow(const QByteArray &resource, QWindow *window) const @@ -637,7 +662,7 @@ void *QXcbNativeInterface::handlerNativeResourceForWindow(const QByteArray &reso NativeResourceForWindowFunction func = handlerNativeResourceFunctionForWindow(resource); if (func) return func(window); - return Q_NULLPTR; + return nullptr; } void *QXcbNativeInterface::handlerNativeResourceForBackingStore(const QByteArray &resource, QBackingStore *backingStore) const @@ -645,7 +670,63 @@ void *QXcbNativeInterface::handlerNativeResourceForBackingStore(const QByteArray NativeResourceForBackingStoreFunction func = handlerNativeResourceFunctionForBackingStore(resource); if (func) return func(backingStore); - return Q_NULLPTR; + return nullptr; +} + +static void dumpNativeWindowsRecursion(const QXcbConnection *connection, xcb_window_t window, + int level, QTextStream &str) +{ + if (level) + str << QByteArray(2 * level, ' '); + + xcb_connection_t *conn = connection->xcb_connection(); + auto geomReply = Q_XCB_REPLY(xcb_get_geometry, conn, window); + if (!geomReply) + return; + const QRect geom(geomReply->x, geomReply->y, geomReply->width, geomReply->height); + if (!geom.isValid() || (geom.width() <= 3 && geom.height() <= 3)) + return; // Skip helper/dummy windows. + str << "0x"; + const int oldFieldWidth = str.fieldWidth(); + const QChar oldPadChar =str.padChar(); + str.setFieldWidth(8); + str.setPadChar(QLatin1Char('0')); + str << hex << window; + str.setFieldWidth(oldFieldWidth); + str.setPadChar(oldPadChar); + str << dec << " \"" + << QXcbWindow::windowTitle(connection, window) << "\" " + << geom.width() << 'x' << geom.height() << forcesign << geom.x() << geom.y() + << noforcesign << '\n'; + + auto reply = Q_XCB_REPLY(xcb_query_tree, conn, window); + if (reply) { + const int count = xcb_query_tree_children_length(reply.get()); + const xcb_window_t *children = xcb_query_tree_children(reply.get()); + for (int i = 0; i < count; ++i) + dumpNativeWindowsRecursion(connection, children[i], level + 1, str); + } +} + +QString QXcbNativeInterface::dumpConnectionNativeWindows(const QXcbConnection *connection, WId root) const +{ + QString result; + QTextStream str(&result); + if (root) { + dumpNativeWindowsRecursion(connection, xcb_window_t(root), 0, str); + } else { + for (const QXcbScreen *screen : connection->screens()) { + str << "Screen: \"" << screen->name() << "\"\n"; + dumpNativeWindowsRecursion(connection, screen->root(), 0, str); + str << '\n'; + } + } + return result; +} + +QString QXcbNativeInterface::dumpNativeWindows(WId root) const +{ + return dumpConnectionNativeWindows(QXcbIntegration::instance()->defaultConnection(), root); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h index 4186d77f4d..6a752c68ca 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,11 +116,19 @@ 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(); Q_INVOKABLE bool requestSystemTrayWindowDock(const QWindow *window); Q_INVOKABLE QRect systemTrayWindowGlobalGeometry(const QWindow *window); + Q_INVOKABLE QString dumpConnectionNativeWindows(const QXcbConnection *connection, WId root) const; + Q_INVOKABLE QString dumpNativeWindows(WId root = 0) const; void addHandler(QXcbNativeInterfaceHandler *handler); void removeHandler(QXcbNativeInterfaceHandler *handler); diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp index 5e136b5d7e..49bf5181cc 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.cpp +++ b/src/plugins/platforms/xcb/qxcbscreen.cpp @@ -65,6 +65,59 @@ 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) + m_windowManagerName = QXcbWindow::windowTitle(connection, windowManager); + } + + 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() @@ -79,7 +132,7 @@ QXcbScreen *QXcbVirtualDesktop::screenAt(const QPoint &pos) const if (screen->virtualDesktop() == this && screen->geometry().contains(pos)) return screen; } - return Q_NULLPTR; + return nullptr; } void QXcbVirtualDesktop::addScreen(QPlatformScreen *s) @@ -123,18 +176,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 +210,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 +234,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 +412,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 +436,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 +480,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 +506,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 +516,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 +538,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,25 +572,19 @@ 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 { - return qt_xcb_imageFormatForVisual(connection(), screen()->root_depth, visualForId(screen()->root_visual)); + QImage::Format format; + qt_xcb_imageFormatForVisual(connection(), screen()->root_depth, visualForId(screen()->root_visual), &format); + return format; } QDpi QXcbScreen::virtualDpi() const @@ -468,8 +600,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 +714,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 +745,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 +773,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 +788,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 +814,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 +852,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 +877,45 @@ QPixmap QXcbScreen::grabWindow(WId window, int xIn, int yIn, int width, int heig return result; } -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) +QXcbXSettings *QXcbScreen::xSettings() 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); + return m_virtualDesktop->xSettings(); } -static QFontEngine::SubpixelAntialiasingType parseXftRgba(const QByteArray& stringValue) +QByteArray QXcbScreen::getOutputProperty(xcb_atom_t atom) const { - 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; + QByteArray result; - 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..4a9b1bd209 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 @@ -110,7 +140,7 @@ class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen public: QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop, xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *outputInfo, - const xcb_xinerama_screen_info_t *xineramaScreenInfo = Q_NULLPTR, int xineramaScreenIdx = -1); + const xcb_xinerama_screen_info_t *xineramaScreenInfo = nullptr, int xineramaScreenIdx = -1); ~QXcbScreen(); QString getOutputName(xcb_randr_get_output_info_reply_t *outputInfo); @@ -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 37d6336a72..61cfed4db7 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -51,6 +51,7 @@ #include "qxcbscreen.h" #include "qxcbdrag.h" #include "qxcbkeyboard.h" +#include "qxcbimage.h" #include "qxcbwmsupport.h" #include "qxcbimage.h" #include "qxcbnativeinterface.h" @@ -176,74 +177,23 @@ static inline bool isTransient(const QWindow *w) || w->type() == Qt::Popup; } -static inline QImage::Format imageFormatForVisual(int depth, quint32 red_mask, quint32 blue_mask, bool *rgbSwap) +void QXcbWindow::setImageFormatForVisual(const xcb_visualtype_t *visual) { - if (rgbSwap) - *rgbSwap = false; - switch (depth) { - case 32: - if (blue_mask == 0xff) - return QImage::Format_ARGB32_Premultiplied; - if (red_mask == 0x3ff) - return QImage::Format_A2BGR30_Premultiplied; - if (blue_mask == 0x3ff) - return QImage::Format_A2RGB30_Premultiplied; - if (red_mask == 0xff) { - if (rgbSwap) - *rgbSwap = true; - return QImage::Format_ARGB32_Premultiplied; - } - break; - case 30: - if (red_mask == 0x3ff) - return QImage::Format_BGR30; - if (blue_mask == 0x3ff) - return QImage::Format_RGB30; - break; - case 24: - if (blue_mask == 0xff) - return QImage::Format_RGB32; - if (red_mask == 0xff) { - if (rgbSwap) - *rgbSwap = true; - return QImage::Format_RGB32; - } - break; - case 16: - if (blue_mask == 0x1f) - return QImage::Format_RGB16; - if (red_mask == 0x1f) { - if (rgbSwap) - *rgbSwap = true; - return QImage::Format_RGB16; - } - break; - case 15: - if (blue_mask == 0x1f) - return QImage::Format_RGB555; - if (red_mask == 0x1f) { - if (rgbSwap) - *rgbSwap = true; - return QImage::Format_RGB555; - } - break; - default: - break; - } - qWarning("Unsupported screen format: depth: %d, red_mask: %x, blue_mask: %x", depth, red_mask, blue_mask); + if (qt_xcb_imageFormatForVisual(connection(), m_depth, visual, &m_imageFormat, &m_imageRgbSwap)) + return; - switch (depth) { + switch (m_depth) { + case 32: case 24: qWarning("Using RGB32 fallback, if this works your X11 server is reporting a bad screen format."); - return QImage::Format_RGB32; + m_imageFormat = QImage::Format_RGB32; + break; case 16: qWarning("Using RGB16 fallback, if this works your X11 server is reporting a bad screen format."); - return QImage::Format_RGB16; + m_imageFormat = QImage::Format_RGB16; default: break; } - - return QImage::Format_Invalid; } static inline bool positionIncludesFrame(QWindow *w) @@ -276,12 +226,12 @@ static inline XTextProperty* qstringToXTP(Display *dpy, const QString& s) tl[1] = 0; errCode = XmbTextListToTextProperty(dpy, tl, 1, XStdICCTextStyle, &tp); if (errCode < 0) - qDebug("XmbTextListToTextProperty result code %d", errCode); + qCDebug(lcQpaXcb, "XmbTextListToTextProperty result code %d", errCode); } if (!mapper || errCode < 0) { mapper = QTextCodec::codecForName("latin1"); if (!mapper || !mapper->canEncode(s)) - return Q_NULLPTR; + return nullptr; #endif static QByteArray qcs; qcs = s.toLatin1(); @@ -316,7 +266,7 @@ static QWindow *childWindowAt(QWindow *win, const QPoint &p) && win->geometry().contains(win->parent()->mapFromGlobal(p))) { return win; } - return Q_NULLPTR; + return nullptr; } static const char *wm_window_type_property_id = "_q_xcb_wm_window_type"; @@ -375,7 +325,7 @@ void QXcbWindow::create() } if (!visual) visual = platformScreen->visualForId(m_visualId); - m_imageFormat = imageFormatForVisual(m_depth, visual->red_mask, visual->blue_mask, &m_imageRgbSwap); + setImageFormatForVisual(visual); connection()->addWindowEventListener(m_window, this); return; } @@ -412,7 +362,7 @@ void QXcbWindow::create() resolveFormat(platformScreen->surfaceFormatFor(window()->requestedFormat())); - const xcb_visualtype_t *visual = Q_NULLPTR; + const xcb_visualtype_t *visual = nullptr; if (connection()->hasDefaultVisualId()) { visual = platformScreen->visualForId(connection()->defaultVisualId()); @@ -420,6 +370,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(); @@ -432,7 +395,7 @@ void QXcbWindow::create() m_visualId = visual->visual_id; m_depth = platformScreen->depthOfVisual(m_visualId); - m_imageFormat = imageFormatForVisual(m_depth, visual->red_mask, visual->blue_mask, &m_imageRgbSwap); + setImageFormatForVisual(visual); quint32 mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL @@ -445,11 +408,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 +428,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 +462,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 +509,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 +538,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 +548,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()); @@ -615,10 +585,10 @@ void QXcbWindow::destroy() if (connection()->focusWindow() == this) doFocusOut(); if (connection()->mouseGrabber() == this) - connection()->setMouseGrabber(Q_NULLPTR); + connection()->setMouseGrabber(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 +599,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 +634,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 +643,16 @@ 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)); + if (window()->parent() && !window()->transientParent()) { + // Wait for server reply for parented windows to ensure that a few window + // moves will come as a one event. This is important when native widget is + // moved a few times in X and Y directions causing native scroll. Widget + // must get single event to not trigger unwanted widget position changes + // and then expose events causing backingstore flushes with incorrect + // offset causing image crruption. + connection()->sync(); + } } xcb_flush(xcb_connection()); @@ -683,12 +662,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 +684,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 +692,6 @@ QMargins QXcbWindow::frameMargins() const window = parent; parent = reply->parent; } - - free(reply); } else { m_dirtyFrameMargins = false; m_frameMargins = QMargins(); @@ -728,23 +701,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 +723,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 +749,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 +781,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 +804,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 +816,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,18 +824,18 @@ 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()); if (connection()->mouseGrabber() == this) - connection()->setMouseGrabber(Q_NULLPTR); + connection()->setMouseGrabber(nullptr); if (QPlatformWindow *w = connection()->mousePressWindow()) { // Unset mousePressWindow when it (or one of its parents) is unmapped while (w) { if (w == this) { - connection()->setMousePressWindow(Q_NULLPTR); + connection()->setMousePressWindow(nullptr); break; } w = w->parent(); @@ -892,7 +853,7 @@ void QXcbWindow::hide() // Find the top level window at cursor position. // Don't use QGuiApplication::topLevelAt(): search only the virtual siblings of this window's screen - QWindow *enterWindow = Q_NULLPTR; + QWindow *enterWindow = nullptr; const auto screens = xcbScreen()->virtualSiblings(); for (QPlatformScreen *screen : screens) { if (screen->geometry().contains(cursorPos)) { @@ -1014,15 +975,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 +988,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 +1011,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 +1034,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 +1047,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 +1074,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 +1201,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 +1314,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 +1350,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 +1447,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 +1475,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 +1512,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 +1531,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 +1632,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 +1680,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 +1738,6 @@ QXcbWindowFunctions::WmWindowTypes QXcbWindow::wmWindowTypes() const break; } } - free(reply); } return result; } @@ -1896,20 +1820,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 +1846,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 +1937,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 @@ -2033,13 +1953,14 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even return; if (event->type == atom(QXcbAtom::WM_PROTOCOLS)) { - if (event->data.data32[0] == atom(QXcbAtom::WM_DELETE_WINDOW)) { + xcb_atom_t protocolAtom = event->data.data32[0]; + if (protocolAtom == atom(QXcbAtom::WM_DELETE_WINDOW)) { QWindowSystemInterface::handleCloseEvent(window()); - } else if (event->data.data32[0] == atom(QXcbAtom::WM_TAKE_FOCUS)) { + } else if (protocolAtom == atom(QXcbAtom::WM_TAKE_FOCUS)) { connection()->setTime(event->data.data32[1]); relayFocusToModalWindow(); return; - } else if (event->data.data32[0] == atom(QXcbAtom::_NET_WM_PING)) { + } else if (protocolAtom == atom(QXcbAtom::_NET_WM_PING)) { if (event->window == xcbScreen()->root()) return; @@ -2048,20 +1969,23 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even reply.response_type = XCB_CLIENT_MESSAGE; reply.window = xcbScreen()->root(); - xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&reply); + xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (const char *)&reply); xcb_flush(xcb_connection()); - } else if (event->data.data32[0] == atom(QXcbAtom::_NET_WM_SYNC_REQUEST)) { + } else if (protocolAtom == atom(QXcbAtom::_NET_WM_SYNC_REQUEST)) { connection()->setTime(event->data.data32[1]); m_syncValue.lo = event->data.data32[2]; m_syncValue.hi = event->data.data32[3]; if (m_usingSyncProtocol) m_syncState = SyncReceived; #ifndef QT_NO_WHATSTHIS - } else if (event->data.data32[0] == atom(QXcbAtom::_NET_WM_CONTEXT_HELP)) { + } else if (protocolAtom == atom(QXcbAtom::_NET_WM_CONTEXT_HELP)) { QWindowSystemInterface::handleEnterWhatsThisEvent(); #endif } else { - qWarning() << "QXcbWindow: Unhandled WM_PROTOCOLS message:" << connection()->atomName(event->data.data32[0]); + qCWarning(lcQpaXcb, "Unhandled WM_PROTOCOLS (%s)", + connection()->atomName(protocolAtom).constData()); } #ifndef QT_NO_DRAGANDDROP } else if (event->type == atom(QXcbAtom::XdndEnter)) { @@ -2089,7 +2013,7 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even || event->type == atom(QXcbAtom::_GTK_LOAD_ICONTHEMES)) { //silence the _COMPIZ and _GTK messages for now } else { - qWarning() << "QXcbWindow: Unhandled client message:" << connection()->atomName(event->type); + qCWarning(lcQpaXcb) << "Unhandled client message: " << connection()->atomName(event->type); } } @@ -2099,13 +2023,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 +2036,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 +2074,12 @@ QPoint QXcbWindow::mapToGlobal(const QPoint &pos) const return QPlatformWindow::mapToGlobal(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 +2091,12 @@ QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const return QPlatformWindow::mapFromGlobal(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; @@ -2214,7 +2122,8 @@ void QXcbWindow::handleUnmapNotifyEvent(const xcb_unmap_notify_event_t *event) } void QXcbWindow::handleButtonPressEvent(int event_x, int event_y, int root_x, int root_y, - int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, Qt::MouseEventSource source) + int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, + QEvent::Type type, Qt::MouseEventSource source) { const bool isWheel = detail >= 4 && detail <= 7; if (!isWheel && window() != QGuiApplication::focusWindow()) { @@ -2240,26 +2149,35 @@ 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; } connection()->setMousePressWindow(this); - handleMouseEvent(timestamp, local, global, modifiers, source); + handleMouseEvent(timestamp, local, global, modifiers, type, source); } void QXcbWindow::handleButtonReleaseEvent(int event_x, int event_y, int root_x, int root_y, - int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, Qt::MouseEventSource source) + int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, + QEvent::Type type, Qt::MouseEventSource source) { QPoint local(event_x, event_y); QPoint global(root_x, root_y); @@ -2269,10 +2187,10 @@ void QXcbWindow::handleButtonReleaseEvent(int event_x, int event_y, int root_x, return; } - if (connection()->buttons() == Qt::NoButton) - connection()->setMousePressWindow(Q_NULLPTR); + if (connection()->buttonState() == Qt::NoButton) + connection()->setMousePressWindow(nullptr); - handleMouseEvent(timestamp, local, global, modifiers, source); + handleMouseEvent(timestamp, local, global, modifiers, type, source); } static inline bool doCheckUnGrabAncestor(QXcbConnection *conn) @@ -2284,9 +2202,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 @@ -2294,7 +2213,7 @@ static inline bool doCheckUnGrabAncestor(QXcbConnection *conn) return true; } -static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn = Q_NULLPTR) +static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn = nullptr) { return ((doCheckUnGrabAncestor(conn) && mode == XCB_NOTIFY_MODE_GRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR) @@ -2303,7 +2222,7 @@ static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn = || detail == XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL); } -static bool ignoreEnterEvent(quint8 mode, quint8 detail, QXcbConnection *conn = Q_NULLPTR) +static bool ignoreEnterEvent(quint8 mode, quint8 detail, QXcbConnection *conn = nullptr) { return ((doCheckUnGrabAncestor(conn) && mode == XCB_NOTIFY_MODE_UNGRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR) @@ -2335,7 +2254,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); @@ -2371,51 +2291,51 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y, } void QXcbWindow::handleMotionNotifyEvent(int event_x, int event_y, int root_x, int root_y, - Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, Qt::MouseEventSource source) + Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, + QEvent::Type type, Qt::MouseEventSource source) { QPoint local(event_x, event_y); QPoint global(root_x, root_y); // "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 hasMousePressWindow = (connection()->mousePressWindow() != Q_NULLPTR); + const bool isMouseButtonPressed = (connection()->buttonState() != Qt::NoButton); + const bool hasMousePressWindow = (connection()->mousePressWindow() != nullptr); if (isMouseButtonPressed && !hasMousePressWindow) connection()->setMousePressWindow(this); else if (hasMousePressWindow && !isMouseButtonPressed) - connection()->setMousePressWindow(Q_NULLPTR); + connection()->setMousePressWindow(nullptr); - handleMouseEvent(timestamp, local, global, modifiers, source); + handleMouseEvent(timestamp, local, global, modifiers, type, 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); handleButtonPressEvent(event->event_x, event->event_y, event->root_x, event->root_y, event->detail, - modifiers, event->time); + modifiers, event->time, QEvent::MouseButtonPress); } void QXcbWindow::handleButtonReleaseEvent(const xcb_button_release_event_t *event) { Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state); handleButtonReleaseEvent(event->event_x, event->event_y, event->root_x, event->root_y, event->detail, - modifiers, event->time); + modifiers, event->time, QEvent::MouseButtonRelease); } void QXcbWindow::handleMotionNotifyEvent(const xcb_motion_notify_event_t *event) { Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state); - handleMotionNotifyEvent(event->event_x, event->event_y, event->root_x, event->root_y, modifiers, event->time); + handleMotionNotifyEvent(event->event_x, event->event_y, event->root_x, event->root_y, modifiers, + event->time, QEvent::MouseMove); } -#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 +2353,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,19 +2377,19 @@ 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); - handleButtonPressEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time, source); + conn->setButtonState(button, true); + handleButtonPressEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time, QEvent::MouseButtonPress, 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); - handleButtonReleaseEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time, source); + conn->setButtonState(button, false); + handleButtonReleaseEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time, QEvent::MouseButtonRelease, source); break; case XI_Motion: if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, "XI2 mouse motion %d,%d, time %d, source %s", event_x, event_y, ev->time, sourceName); - handleMotionNotifyEvent(event_x, event_y, root_x, root_y, modifiers, ev->time, source); + handleMotionNotifyEvent(event_x, event_y, root_x, root_y, modifiers, ev->time, QEvent::MouseMove, source); break; default: qWarning() << "Unrecognized XI2 mouse event" << ev->evtype; @@ -2477,7 +2397,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 +2416,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; @@ -2513,10 +2434,13 @@ void QXcbWindow::handleXIEnterLeave(xcb_ge_event_t *event) QXcbWindow *QXcbWindow::toWindow() { return this; } void QXcbWindow::handleMouseEvent(xcb_timestamp_t time, const QPoint &local, const QPoint &global, - Qt::KeyboardModifiers modifiers, Qt::MouseEventSource source) + Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source) { connection()->setTime(time); - QWindowSystemInterface::handleMouseEvent(window(), time, local, global, connection()->buttons(), modifiers, source); + Qt::MouseButton button = type == QEvent::MouseMove ? Qt::NoButton : connection()->button(); + QWindowSystemInterface::handleMouseEvent(window(), time, local, global, + connection()->buttonState(), button, + type, modifiers, source); } void QXcbWindow::handleEnterNotifyEvent(const xcb_enter_notify_event_t *event) @@ -2539,45 +2463,34 @@ 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) - connection()->setMouseGrabber(Q_NULLPTR); + if ((m_windowState & Qt::WindowMinimized) && connection()->mouseGrabber() == this) + connection()->setMouseGrabber(nullptr); } return; } else if (event->atom == atom(QXcbAtom::_NET_FRAME_EXTENTS)) { @@ -2611,7 +2524,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,44 +2548,44 @@ 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()) { + connection()->setMouseGrabber(nullptr); + + if (grab && !connection()->canGrab()) + return false; + +#if QT_CONFIG(xinput2) + if (connection()->hasXInput2() && !connection()->xi2MouseEventsDisabled()) { bool result = connection()->xi2SetMouseGrabEnabled(m_window, grab); if (grab && result) connection()->setMouseGrabber(this); return result; } #endif - if (grab && !connection()->canGrab()) - return false; if (!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; @@ -2727,18 +2640,28 @@ void QXcbWindow::windowEvent(QEvent *event) bool QXcbWindow::startSystemResize(const QPoint &pos, Qt::Corner corner) { + return startSystemMoveResize(pos, corner); +} + +bool QXcbWindow::startSystemMove(const QPoint &pos) +{ + return startSystemMoveResize(pos, 4); +} + +bool QXcbWindow::startSystemMoveResize(const QPoint &pos, int corner) +{ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE); if (!connection()->wmSupport()->isSupportedByWM(moveResize)) return false; const QPoint globalPos = QHighDpi::toNativePixels(window()->mapToGlobal(pos), window()->screen()); #ifdef XCB_USE_XINPUT22 - if (connection()->startSystemResizeForTouchBegin(m_window, globalPos, corner)) + if (connection()->startSystemMoveResizeForTouchBegin(m_window, globalPos, corner)) return true; #endif - return doStartSystemResize(globalPos, corner); + return doStartSystemMoveResize(globalPos, corner); } -bool QXcbWindow::doStartSystemResize(const QPoint &globalPos, Qt::Corner corner) +bool QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int corner) { const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE); xcb_client_message_event_t xev; @@ -2749,12 +2672,16 @@ bool QXcbWindow::doStartSystemResize(const QPoint &globalPos, Qt::Corner corner) xev.format = 32; xev.data.data32[0] = globalPos.x(); xev.data.data32[1] = globalPos.y(); - const bool bottom = corner == Qt::BottomRightCorner || corner == Qt::BottomLeftCorner; - const bool left = corner == Qt::BottomLeftCorner || corner == Qt::TopLeftCorner; - if (bottom) - xev.data.data32[2] = left ? 6 : 4; // bottomleft/bottomright - else - xev.data.data32[2] = left ? 0 : 2; // topleft/topright + if (corner == 4) { + xev.data.data32[2] = 8; // move + } else { + const bool bottom = corner == Qt::BottomRightCorner || corner == Qt::BottomLeftCorner; + const bool left = corner == Qt::BottomLeftCorner || corner == Qt::TopLeftCorner; + if (bottom) + xev.data.data32[2] = left ? 6 : 4; // bottomleft/bottomright + else + xev.data.data32[2] = left ? 0 : 2; // topleft/topright + } xev.data.data32[3] = XCB_BUTTON_INDEX_1; xev.data.data32[4] = 0; xcb_ungrab_pointer(connection()->xcb_connection(), XCB_CURRENT_TIME); @@ -2780,8 +2707,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 +2729,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 +2781,23 @@ 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); +} + +QVector<xcb_rectangle_t> qRegionToXcbRectangleList(const QRegion ®ion) +{ + QVector<xcb_rectangle_t> rects; + rects.reserve(region.rectCount()); + for (const QRect &r : region) + rects.push_back(qRectToXCBRectangle(r)); + return rects; } void QXcbWindow::setMask(const QRegion ®ion) @@ -2873,10 +2808,7 @@ void QXcbWindow::setMask(const QRegion ®ion) xcb_shape_mask(connection()->xcb_connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, xcb_window(), 0, 0, XCB_NONE); } else { - QVector<xcb_rectangle_t> rects; - rects.reserve(region.rectCount()); - for (const QRect &r : region) - rects.push_back(qRectToXCBRectangle(r)); + const auto rects = qRegionToXcbRectangleList(region); xcb_shape_rectangles(connection()->xcb_connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, xcb_window(), 0, 0, rects.size(), &rects[0]); @@ -2917,5 +2849,18 @@ QXcbScreen *QXcbWindow::xcbScreen() const return static_cast<QXcbScreen *>(screen()); } +QString QXcbWindow::windowTitle(const QXcbConnection *conn, xcb_window_t window) +{ + const xcb_atom_t utf8Atom = conn->atom(QXcbAtom::UTF8_STRING); + auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(), + false, window, conn->atom(QXcbAtom::_NET_WM_NAME), + utf8Atom, 0, 1024); + if (reply && reply->format == 8 && reply->type == utf8Atom) { + const char *name = reinterpret_cast<const char *>(xcb_get_property_value(reply.get())); + return QString::fromUtf8(name); + } + return QString(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h index f38343b6c2..957c4e9cbd 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; @@ -110,6 +110,7 @@ public: void windowEvent(QEvent *event) override; bool startSystemResize(const QPoint &pos, Qt::Corner corner) override; + bool startSystemMove(const QPoint &pos) override; void setOpacity(qreal level) override; void setMask(const QRegion ®ion) override; @@ -138,7 +139,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 @@ -146,7 +147,7 @@ public: QXcbWindow *toWindow() override; void handleMouseEvent(xcb_timestamp_t time, const QPoint &local, const QPoint &global, - Qt::KeyboardModifiers modifiers, Qt::MouseEventSource source); + Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source); void updateNetWmUserTime(xcb_timestamp_t timestamp); @@ -177,17 +178,21 @@ public: QXcbScreen *xcbScreen() const; - bool doStartSystemResize(const QPoint &globalPos, Qt::Corner corner); + bool startSystemMoveResize(const QPoint &pos, int corner); + bool doStartSystemMoveResize(const QPoint &globalPos, int corner); virtual void create(); virtual void destroy(); + static QString windowTitle(const QXcbConnection *conn, xcb_window_t window); + public Q_SLOTS: void updateSyncRequestCounter(); protected: virtual void resolveFormat(const QSurfaceFormat &format) { m_format = format; } virtual const xcb_visualtype_t *createVisual(); + void setImageFormatForVisual(const xcb_visualtype_t *visual); QXcbScreen *parentScreen(); @@ -220,13 +225,16 @@ protected: bool compressExposeEvent(QRegion &exposeRegion); void handleButtonPressEvent(int event_x, int event_y, int root_x, int root_y, - int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); + int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, + QEvent::Type type, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); void handleButtonReleaseEvent(int event_x, int event_y, int root_x, int root_y, - int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); + int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, + QEvent::Type type, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); void handleMotionNotifyEvent(int event_x, int event_y, int root_x, int root_y, - Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); + Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, + QEvent::Type type, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); void handleEnterNotifyEvent(int event_x, int event_y, int root_x, int root_y, quint8 mode, quint8 detail, xcb_timestamp_t timestamp); @@ -244,7 +252,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 +262,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 +274,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, @@ -290,6 +300,8 @@ protected: void create() override {} // No-op }; +QVector<xcb_rectangle_t> qRegionToXcbRectangleList(const QRegion ®ion); + QT_END_NAMESPACE Q_DECLARE_METATYPE(QXcbWindow*) 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-plugin.pro b/src/plugins/platforms/xcb/xcb-plugin.pro index 01d493156d..a2c56a3dcf 100644 --- a/src/plugins/platforms/xcb/xcb-plugin.pro +++ b/src/plugins/platforms/xcb/xcb-plugin.pro @@ -4,6 +4,8 @@ QT += core-private gui-private xcb_qpa_lib-private DEFINES += QT_NO_FOREACH +macos: CONFIG += no_app_extension_api_only + SOURCES = \ qxcbmain.cpp OTHER_FILES += xcb.json README 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 |