/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2013 Samuel Gaist ** 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 "qwindowsintegration.h" #include "qwindowswindow.h" #include "qwindowscontext.h" #include "qwin10helpers.h" #include "qwindowsmenu.h" #include "qwindowsopenglcontext.h" #include "qwindowsscreen.h" #include "qwindowstheme.h" #include "qwindowsservices.h" #include #if QT_CONFIG(directwrite3) #include #endif #ifndef QT_NO_FREETYPE # include #endif #include #if QT_CONFIG(clipboard) # include "qwindowsclipboard.h" # if QT_CONFIG(draganddrop) # include "qwindowsdrag.h" # endif #endif #include "qwindowsinputcontext.h" #include "qwindowskeymapper.h" #if QT_CONFIG(accessibility) # include "uiautomation/qwindowsuiaaccessibility.h" #endif #include #include #if QT_CONFIG(sessionmanager) # include "qwindowssessionmanager.h" #endif #include #include #include #include #include #include #include #include #include #if !defined(QT_NO_OPENGL) # include "qwindowsglcontext.h" #endif #include "qwindowsopengltester.h" static inline void initOpenGlBlacklistResources() { Q_INIT_RESOURCE(openglblacklists); } QT_BEGIN_NAMESPACE /*! \class QWindowsIntegration \brief QPlatformIntegration implementation for Windows. \internal \section1 Programming Considerations The platform plugin should run on Desktop Windows from Windows XP onwards and Windows Embedded. It should compile with: \list \li Microsoft Visual Studio 2013 or later (using the Microsoft Windows SDK, (\c Q_CC_MSVC). \li Stock \l{http://mingw.org/}{MinGW} (\c Q_CC_MINGW). This version ships with headers that are missing a lot of WinAPI. \li MinGW distributions using GCC 4.7 or higher and a recent MinGW-w64 runtime API, such as \l{http://tdm-gcc.tdragon.net/}{TDM-GCC}, or \l{http://mingwbuilds.sourceforge.net/}{MinGW-builds} (\c Q_CC_MINGW and \c __MINGW64_VERSION_MAJOR indicating the version). MinGW-w64 provides more complete headers (compared to stock MinGW from mingw.org), including a considerable part of the Windows SDK. \endlist */ struct QWindowsIntegrationPrivate { Q_DISABLE_COPY_MOVE(QWindowsIntegrationPrivate) explicit QWindowsIntegrationPrivate() = default; ~QWindowsIntegrationPrivate(); void parseOptions(QWindowsIntegration *q, const QStringList ¶mList); unsigned m_options = 0; QWindowsContext m_context; QPlatformFontDatabase *m_fontDatabase = nullptr; #if QT_CONFIG(clipboard) QWindowsClipboard m_clipboard; # if QT_CONFIG(draganddrop) QWindowsDrag m_drag; # endif #endif #ifndef QT_NO_OPENGL QMutex m_staticContextLock; QScopedPointer m_staticOpenGLContext; #endif // QT_NO_OPENGL QScopedPointer m_inputContext; #if QT_CONFIG(accessibility) QWindowsUiaAccessibility m_accessibility; #endif QWindowsServices m_services; }; template bool parseIntOption(const QString ¶meter,const QLatin1String &option, IntType minimumValue, IntType maximumValue, IntType *target) { const int valueLength = parameter.size() - option.size() - 1; if (valueLength < 1 || !parameter.startsWith(option) || parameter.at(option.size()) != u'=') return false; bool ok; const auto valueRef = QStringView{parameter}.right(valueLength); const int value = valueRef.toInt(&ok); if (ok) { if (value >= minimumValue && value <= maximumValue) *target = static_cast(value); else { qWarning() << "Value" << value << "for option" << option << "out of range" << minimumValue << ".." << maximumValue; } } else { qWarning() << "Invalid value" << valueRef << "for option" << option; } return true; } using DarkModeHandlingFlag = QNativeInterface::Private::QWindowsApplication::DarkModeHandlingFlag; using DarkModeHandling = QNativeInterface::Private::QWindowsApplication::DarkModeHandling; static inline unsigned parseOptions(const QStringList ¶mList, int *tabletAbsoluteRange, QtWindows::ProcessDpiAwareness *dpiAwareness, DarkModeHandling *darkModeHandling) { unsigned options = 0; for (const QString ¶m : paramList) { if (param.startsWith(u"fontengine=")) { if (param.endsWith(u"directwrite")) { options |= QWindowsIntegration::FontDatabaseDirectWrite; } else if (param.endsWith(u"freetype")) { options |= QWindowsIntegration::FontDatabaseFreeType; } else if (param.endsWith(u"native")) { options |= QWindowsIntegration::FontDatabaseNative; } } else if (param.startsWith(u"dialogs=")) { if (param.endsWith(u"xp")) { options |= QWindowsIntegration::XpNativeDialogs; } else if (param.endsWith(u"none")) { options |= QWindowsIntegration::NoNativeDialogs; } } else if (param == u"altgr") { options |= QWindowsIntegration::DetectAltGrModifier; } else if (param == u"gl=gdi") { options |= QWindowsIntegration::DisableArb; } else if (param == u"nodirectwrite") { options |= QWindowsIntegration::DontUseDirectWriteFonts; } else if (param == u"nocolorfonts") { options |= QWindowsIntegration::DontUseColorFonts; } else if (param == u"nomousefromtouch") { options |= QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch; } 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::ProcessPerMonitorV2DpiAware, dpiAwareness)) { } else if (param == u"menus=native") { options |= QWindowsIntegration::AlwaysUseNativeMenus; } else if (param == u"menus=none") { options |= QWindowsIntegration::NoNativeMenus; } else if (param == u"nowmpointer") { options |= QWindowsIntegration::DontUseWMPointer; } else if (param == u"reverse") { options |= QWindowsIntegration::RtlEnabled; } else if (param == u"darkmode=1") { darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeWindowFrames); } else if (param == u"darkmode=2") { darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeWindowFrames); darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeStyle); } else { qWarning() << "Unknown option" << param; } } return options; } void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStringList ¶mList) { initOpenGlBlacklistResources(); static bool dpiAwarenessSet = false; // Default to per-monitor-v2 awareness (if available) QtWindows::ProcessDpiAwareness dpiAwareness = QtWindows::ProcessPerMonitorV2DpiAware; int tabletAbsoluteRange = -1; DarkModeHandling darkModeHandling; m_options = ::parseOptions(paramList, &tabletAbsoluteRange, &dpiAwareness, &darkModeHandling); q->setDarkModeHandling(darkModeHandling); QWindowsFontDatabase::setFontOptions(m_options); if (tabletAbsoluteRange >= 0) QWindowsContext::setTabletAbsoluteRange(tabletAbsoluteRange); if (m_context.initPointer(m_options)) QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); else m_context.initTablet(); if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication. if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) { // DpiAwareV2 requires using new API if (dpiAwareness == QtWindows::ProcessPerMonitorV2DpiAware) { m_context.setProcessDpiV2Awareness(); qCDebug(lcQpaWindows) << __FUNCTION__ << "DpiAwareness: DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2"; } else { m_context.setProcessDpiAwareness(dpiAwareness); qCDebug(lcQpaWindows) << __FUNCTION__ << "DpiAwareness=" << dpiAwareness << "effective process DPI awareness=" << QWindowsContext::processDpiAwareness(); } } dpiAwarenessSet = true; } m_context.initTouch(m_options); QPlatformCursor::setCapability(QPlatformCursor::OverrideCursor); m_context.initPowerNotificationHandler(); } QWindowsIntegrationPrivate::~QWindowsIntegrationPrivate() { delete m_fontDatabase; } QWindowsIntegration *QWindowsIntegration::m_instance = nullptr; QWindowsIntegration::QWindowsIntegration(const QStringList ¶mList) : d(new QWindowsIntegrationPrivate) { m_instance = this; d->parseOptions(this, paramList); #if QT_CONFIG(clipboard) d->m_clipboard.registerViewer(); #endif d->m_context.screenManager().handleScreenChanges(); d->m_context.setDetectAltGrModifier((d->m_options & DetectAltGrModifier) != 0); } QWindowsIntegration::~QWindowsIntegration() { m_instance = nullptr; } void QWindowsIntegration::initialize() { QString icStr = QPlatformInputContextFactory::requested(); icStr.isNull() ? d->m_inputContext.reset(new QWindowsInputContext) : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStr)); } bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { case ThreadedPixmaps: return true; #ifndef QT_NO_OPENGL case OpenGL: return true; case ThreadedOpenGL: if (const QWindowsStaticOpenGLContext *glContext = QWindowsIntegration::staticOpenGLContext()) return glContext->supportsThreadedOpenGL(); return false; #endif // !QT_NO_OPENGL case WindowMasks: return true; case MultipleWindows: return true; case ForeignWindows: return true; case RasterGLSurface: return true; case AllGLFunctionsQueryable: return true; case SwitchableWidgetComposition: return false; // QTBUG-68329 QTBUG-53515 QTBUG-54734 default: return QPlatformIntegration::hasCapability(cap); } return false; } QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) const { if (window->type() == Qt::Desktop) { auto *result = new QWindowsDesktopWindow(window); qCDebug(lcQpaWindows) << "Desktop window:" << window << Qt::showbase << Qt::hex << result->winId() << Qt::noshowbase << Qt::dec << result->geometry(); return result; } QWindowsWindowData requested; requested.flags = window->flags(); requested.geometry = window->isTopLevel() ? QHighDpi::toNativePixels(window->geometry(), window) : QHighDpi::toNativeLocalPosition(window->geometry(), window); // Apply custom margins (see QWindowsWindow::setCustomMargins())). const QVariant customMarginsV = window->property("_q_windowsCustomMargins"); if (customMarginsV.isValid()) requested.customMargins = qvariant_cast(customMarginsV); QWindowsWindowData obtained = QWindowsWindowData::create(window, requested, QWindowsWindow::formatWindowTitle(window->title())); qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << ' ' << window << "\n Requested: " << requested.geometry << " frame incl.=" << QWindowsGeometryHint::positionIncludesFrame(window) << ' ' << requested.flags << "\n Obtained : " << obtained.geometry << " margins=" << obtained.fullFrameMargins << " handle=" << obtained.hwnd << ' ' << obtained.flags << '\n'; if (Q_UNLIKELY(!obtained.hwnd)) return nullptr; QWindowsWindow *result = createPlatformWindowHelper(window, obtained); Q_ASSERT(result); if (window->isTopLevel() && !QWindowsContext::shouldHaveNonClientDpiScaling(window)) result->setFlag(QWindowsWindow::DisableNonClientScaling); if (QWindowsMenuBar *menuBarToBeInstalled = QWindowsMenuBar::menuBarOf(window)) menuBarToBeInstalled->install(result); return result; } QPlatformWindow *QWindowsIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const { const HWND hwnd = reinterpret_cast(nativeHandle); if (!IsWindow(hwnd)) { qWarning("Windows QPA: Invalid foreign window ID %p.", hwnd); return nullptr; } auto *result = new QWindowsForeignWindow(window, hwnd); const QRect obtainedGeometry = result->geometry(); QScreen *screen = nullptr; if (const QPlatformScreen *pScreen = result->screenForGeometry(obtainedGeometry)) screen = pScreen->screen(); if (screen && screen != window->screen()) window->setScreen(screen); qCDebug(lcQpaWindows) << "Foreign window:" << window << Qt::showbase << Qt::hex << result->winId() << Qt::noshowbase << Qt::dec << obtainedGeometry << screen; return result; } // Overridden to return a QWindowsDirect2DWindow in Direct2D plugin. QWindowsWindow *QWindowsIntegration::createPlatformWindowHelper(QWindow *window, const QWindowsWindowData &data) const { return new QWindowsWindow(window, data); } #ifndef QT_NO_OPENGL QWindowsStaticOpenGLContext *QWindowsStaticOpenGLContext::doCreate() { #if defined(QT_OPENGL_DYNAMIC) QWindowsOpenGLTester::Renderer requestedRenderer = QWindowsOpenGLTester::requestedRenderer(); switch (requestedRenderer) { case QWindowsOpenGLTester::DesktopGl: if (QWindowsStaticOpenGLContext *glCtx = QOpenGLStaticContext::create()) { if ((QWindowsOpenGLTester::supportedRenderers(requestedRenderer) & QWindowsOpenGLTester::DisableRotationFlag) && !QWindowsScreen::setOrientationPreference(Qt::LandscapeOrientation)) { qCWarning(lcQpaGl, "Unable to disable rotation."); } return glCtx; } qCWarning(lcQpaGl, "System OpenGL failed. Falling back to Software OpenGL."); return QOpenGLStaticContext::create(true); case QWindowsOpenGLTester::SoftwareRasterizer: if (QWindowsStaticOpenGLContext *swCtx = QOpenGLStaticContext::create(true)) return swCtx; qCWarning(lcQpaGl, "Software OpenGL failed. Falling back to system OpenGL."); if (QWindowsOpenGLTester::supportedRenderers(requestedRenderer) & QWindowsOpenGLTester::DesktopGl) return QOpenGLStaticContext::create(); return nullptr; default: break; } const QWindowsOpenGLTester::Renderers supportedRenderers = QWindowsOpenGLTester::supportedRenderers(requestedRenderer); if (supportedRenderers.testFlag(QWindowsOpenGLTester::DisableProgramCacheFlag) && !QCoreApplication::testAttribute(Qt::AA_DisableShaderDiskCache)) { QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache); } if (supportedRenderers & QWindowsOpenGLTester::DesktopGl) { if (QWindowsStaticOpenGLContext *glCtx = QOpenGLStaticContext::create()) { if ((supportedRenderers & QWindowsOpenGLTester::DisableRotationFlag) && !QWindowsScreen::setOrientationPreference(Qt::LandscapeOrientation)) { qCWarning(lcQpaGl, "Unable to disable rotation."); } return glCtx; } } return QOpenGLStaticContext::create(true); #else return QOpenGLStaticContext::create(); #endif } QWindowsStaticOpenGLContext *QWindowsStaticOpenGLContext::create() { return QWindowsStaticOpenGLContext::doCreate(); } QPlatformOpenGLContext *QWindowsIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { qCDebug(lcQpaGl) << __FUNCTION__ << context->format(); if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { QScopedPointer result(staticOpenGLContext->createContext(context)); if (result->isValid()) return result.take(); } return nullptr; } QOpenGLContext::OpenGLModuleType QWindowsIntegration::openGLModuleType() { #if !defined(QT_OPENGL_DYNAMIC) return QOpenGLContext::LibGL; #else if (const QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) return staticOpenGLContext->moduleType(); return QOpenGLContext::LibGL; #endif } HMODULE QWindowsIntegration::openGLModuleHandle() const { if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) return static_cast(staticOpenGLContext->moduleHandle()); return nullptr; } QOpenGLContext *QWindowsIntegration::createOpenGLContext(HGLRC ctx, HWND window, QOpenGLContext *shareContext) const { if (!ctx || !window) return nullptr; if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { QScopedPointer result(staticOpenGLContext->createContext(ctx, window)); if (result->isValid()) { auto *context = new QOpenGLContext; context->setShareContext(shareContext); auto *contextPrivate = QOpenGLContextPrivate::get(context); contextPrivate->adopt(result.take()); return context; } } return nullptr; } QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext() { QWindowsIntegration *integration = QWindowsIntegration::instance(); if (!integration) return nullptr; QWindowsIntegrationPrivate *d = integration->d.data(); QMutexLocker lock(&d->m_staticContextLock); if (d->m_staticOpenGLContext.isNull()) d->m_staticOpenGLContext.reset(QWindowsStaticOpenGLContext::create()); return d->m_staticOpenGLContext.data(); } #endif // !QT_NO_OPENGL QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const { if (!d->m_fontDatabase) { #if QT_CONFIG(directwrite3) if (d->m_options & QWindowsIntegration::FontDatabaseDirectWrite) d->m_fontDatabase = new QWindowsDirectWriteFontDatabase; else #endif #ifndef QT_NO_FREETYPE if (d->m_options & QWindowsIntegration::FontDatabaseFreeType) d->m_fontDatabase = new QWindowsFontDatabaseFT; else #endif // QT_NO_FREETYPE d->m_fontDatabase = new QWindowsFontDatabase(); } return d->m_fontDatabase; } #ifdef SPI_GETKEYBOARDSPEED static inline int keyBoardAutoRepeatRateMS() { DWORD time = 0; if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &time, 0)) return time ? 1000 / static_cast(time) : 500; return 30; } #endif QVariant QWindowsIntegration::styleHint(QPlatformIntegration::StyleHint hint) const { switch (hint) { case QPlatformIntegration::CursorFlashTime: if (const unsigned timeMS = GetCaretBlinkTime()) return QVariant(timeMS != INFINITE ? int(timeMS) * 2 : 0); break; #ifdef SPI_GETKEYBOARDSPEED case KeyboardAutoRepeatRate: return QVariant(keyBoardAutoRepeatRateMS()); #endif case QPlatformIntegration::ShowIsMaximized: case QPlatformIntegration::StartDragTime: case QPlatformIntegration::StartDragDistance: case QPlatformIntegration::KeyboardInputInterval: case QPlatformIntegration::ShowIsFullScreen: case QPlatformIntegration::PasswordMaskDelay: case QPlatformIntegration::StartDragVelocity: break; // Not implemented case QPlatformIntegration::FontSmoothingGamma: return QVariant(QWindowsFontDatabase::fontSmoothingGamma()); case QPlatformIntegration::MouseDoubleClickInterval: if (const UINT ms = GetDoubleClickTime()) return QVariant(int(ms)); break; case QPlatformIntegration::UseRtlExtensions: return QVariant(d->m_context.useRTLExtensions()); default: break; } return QPlatformIntegration::styleHint(hint); } Qt::KeyboardModifiers QWindowsIntegration::queryKeyboardModifiers() const { return QWindowsKeyMapper::queryKeyboardModifiers(); } QList QWindowsIntegration::possibleKeys(const QKeyEvent *e) const { return d->m_context.possibleKeys(e); } #if QT_CONFIG(clipboard) QPlatformClipboard * QWindowsIntegration::clipboard() const { return &d->m_clipboard; } # if QT_CONFIG(draganddrop) QPlatformDrag *QWindowsIntegration::drag() const { return &d->m_drag; } # endif // QT_CONFIG(draganddrop) #endif // !QT_NO_CLIPBOARD QPlatformInputContext * QWindowsIntegration::inputContext() const { return d->m_inputContext.data(); } #if QT_CONFIG(accessibility) QPlatformAccessibility *QWindowsIntegration::accessibility() const { return &d->m_accessibility; } #endif unsigned QWindowsIntegration::options() const { return d->m_options; } #if QT_CONFIG(sessionmanager) QPlatformSessionManager *QWindowsIntegration::createPlatformSessionManager(const QString &id, const QString &key) const { return new QWindowsSessionManager(id, key); } #endif QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const { return new QWindowsGuiEventDispatcher; } QStringList QWindowsIntegration::themeNames() const { return QStringList(QLatin1String(QWindowsTheme::name)); } QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const { if (name == QLatin1String(QWindowsTheme::name)) return new QWindowsTheme; return QPlatformIntegration::createPlatformTheme(name); } QPlatformServices *QWindowsIntegration::services() const { return &d->m_services; } 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