diff options
Diffstat (limited to 'src/plugins/platforms/windows/qwindowsintegration.cpp')
-rw-r--r-- | src/plugins/platforms/windows/qwindowsintegration.cpp | 354 |
1 files changed, 223 insertions, 131 deletions
diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 822c11d0f2..487e1d47b6 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch> -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwindowsintegration.h" #include "qwindowswindow.h" @@ -84,6 +48,11 @@ #include <QtCore/qdebug.h> #include <QtCore/qvariant.h> +#include <QtCore/qoperatingsystemversion.h> +#include <QtCore/private/qfunctions_win_p.h> + +#include <wrl.h> + #include <limits.h> #if !defined(QT_NO_OPENGL) @@ -92,6 +61,16 @@ #include "qwindowsopengltester.h" +#if QT_CONFIG(cpp_winrt) +# include <QtCore/private/qt_winrtbase_p.h> +# include <winrt/Windows.UI.Notifications.h> +# include <winrt/Windows.Data.Xml.Dom.h> +# include <winrt/Windows.Foundation.h> +# include <winrt/Windows.UI.ViewManagement.h> +#endif + +#include <memory> + static inline void initOpenGlBlacklistResources() { Q_INIT_RESOURCE(openglblacklists); @@ -99,38 +78,7 @@ static inline void initOpenGlBlacklistResources() 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 - - When using a function from the WinAPI, the minimum supported Windows version - and Windows Embedded support should be checked. If the function is not supported - on Windows XP or is not present in the MinGW-headers, it should be dynamically - resolved. For this purpose, QWindowsContext has static structs like - QWindowsUser32DLL and QWindowsShell32DLL. All function pointers should go to - these structs to avoid lookups in several places. - -*/ +using namespace Qt::StringLiterals; struct QWindowsIntegrationPrivate { @@ -161,7 +109,7 @@ struct QWindowsIntegrationPrivate }; template <typename IntType> -bool parseIntOption(const QString ¶meter,const QLatin1String &option, +bool parseIntOption(const QString ¶meter,const QLatin1StringView &option, IntType minimumValue, IntType maximumValue, IntType *target) { const int valueLength = parameter.size() - option.size() - 1; @@ -171,7 +119,7 @@ bool parseIntOption(const QString ¶meter,const QLatin1String &option, const auto valueRef = QStringView{parameter}.right(valueLength); const int value = valueRef.toInt(&ok); if (ok) { - if (value >= minimumValue && value <= maximumValue) + if (value >= int(minimumValue) && value <= int(maximumValue)) *target = static_cast<IntType>(value); else { qWarning() << "Value" << value << "for option" << option << "out of range" @@ -188,14 +136,14 @@ using DarkModeHandling = QNativeInterface::Private::QWindowsApplication::DarkMod static inline unsigned parseOptions(const QStringList ¶mList, int *tabletAbsoluteRange, - QtWindows::ProcessDpiAwareness *dpiAwareness, + QtWindows::DpiAwareness *dpiAwareness, DarkModeHandling *darkModeHandling) { unsigned options = 0; for (const QString ¶m : paramList) { if (param.startsWith(u"fontengine=")) { - if (param.endsWith(u"directwrite")) { - options |= QWindowsIntegration::FontDatabaseDirectWrite; + if (param.endsWith(u"gdi")) { + options |= QWindowsIntegration::FontDatabaseGDI; } else if (param.endsWith(u"freetype")) { options |= QWindowsIntegration::FontDatabaseFreeType; } else if (param.endsWith(u"native")) { @@ -217,19 +165,21 @@ static inline unsigned parseOptions(const QStringList ¶mList, 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 (parseIntOption(param, "verbose"_L1, 0, INT_MAX, &QWindowsContext::verbose) + || parseIntOption(param, "tabletabsoluterange"_L1, 0, INT_MAX, tabletAbsoluteRange) + || parseIntOption(param, "dpiawareness"_L1, QtWindows::DpiAwareness::Invalid, + QtWindows::DpiAwareness::PerMonitorVersion2, 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=0") { + *darkModeHandling = {}; } else if (param == u"darkmode=1") { darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeWindowFrames); + darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeStyle, false); } else if (param == u"darkmode=2") { darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeWindowFrames); darkModeHandling->setFlag(DarkModeHandlingFlag::DarkModeStyle); @@ -245,38 +195,26 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr initOpenGlBlacklistResources(); static bool dpiAwarenessSet = false; - static bool hasDpiAwarenessContext = QWindowsContext::user32dll.setProcessDpiAwarenessContext != nullptr; // Default to per-monitor-v2 awareness (if available) - QtWindows::ProcessDpiAwareness dpiAwareness = hasDpiAwarenessContext ? - QtWindows::ProcessPerMonitorV2DpiAware : QtWindows::ProcessPerMonitorDpiAware; + QtWindows::DpiAwareness dpiAwareness = QtWindows::DpiAwareness::PerMonitorVersion2; int tabletAbsoluteRange = -1; - DarkModeHandling darkModeHandling; + DarkModeHandling darkModeHandling = DarkModeHandlingFlag::DarkModeWindowFrames + | DarkModeHandlingFlag::DarkModeStyle; 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(); + QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); 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 && hasDpiAwarenessContext) { - 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(); - } + m_context.setProcessDpiAwareness(dpiAwareness); + qCDebug(lcQpaWindow) << "DpiAwareness=" << dpiAwareness + << "effective process DPI awareness=" << QWindowsContext::processDpiAwareness(); } dpiAwarenessSet = true; } @@ -302,7 +240,7 @@ QWindowsIntegration::QWindowsIntegration(const QStringList ¶mList) : #if QT_CONFIG(clipboard) d->m_clipboard.registerViewer(); #endif - d->m_context.screenManager().handleScreenChanges(); + d->m_context.screenManager().initialize(); d->m_context.setDetectAltGrModifier((d->m_options & DetectAltGrModifier) != 0); } @@ -313,9 +251,9 @@ QWindowsIntegration::~QWindowsIntegration() void QWindowsIntegration::initialize() { - QString icStr = QPlatformInputContextFactory::requested(); - icStr.isNull() ? d->m_inputContext.reset(new QWindowsInputContext) - : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStr)); + auto icStrs = QPlatformInputContextFactory::requested(); + icStrs.isEmpty() ? d->m_inputContext.reset(new QWindowsInputContext) + : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStrs)); } bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) const @@ -343,6 +281,8 @@ bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) co return true; case SwitchableWidgetComposition: return false; // QTBUG-68329 QTBUG-53515 QTBUG-54734 + case BackingStoreStaticContents: + return true; default: return QPlatformIntegration::hasCapability(cap); } @@ -353,7 +293,7 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons { if (window->type() == Qt::Desktop) { auto *result = new QWindowsDesktopWindow(window); - qCDebug(lcQpaWindows) << "Desktop window:" << window + qCDebug(lcQpaWindow) << "Desktop window:" << window << Qt::showbase << Qt::hex << result->winId() << Qt::noshowbase << Qt::dec << result->geometry(); return result; } @@ -363,15 +303,17 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons 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<QMargins>(customMarginsV); + if (!(requested.flags & Qt::FramelessWindowHint)) { + // Apply custom margins (see QWindowsWindow::setCustomMargins())). + const QVariant customMarginsV = window->property("_q_windowsCustomMargins"); + if (customMarginsV.isValid()) + requested.customMargins = qvariant_cast<QMargins>(customMarginsV); + } QWindowsWindowData obtained = QWindowsWindowData::create(window, requested, QWindowsWindow::formatWindowTitle(window->title())); - qCDebug(lcQpaWindows).nospace() + qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << ' ' << window << "\n Requested: " << requested.geometry << " frame incl.=" << QWindowsGeometryHint::positionIncludesFrame(window) @@ -408,7 +350,7 @@ QPlatformWindow *QWindowsIntegration::createForeignWindow(QWindow *window, WId n screen = pScreen->screen(); if (screen && screen != window->screen()) window->setScreen(screen); - qCDebug(lcQpaWindows) << "Foreign window:" << window << Qt::showbase << Qt::hex + qCDebug(lcQpaWindow) << "Foreign window:" << window << Qt::showbase << Qt::hex << result->winId() << Qt::noshowbase << Qt::dec << obtainedGeometry << screen; return result; } @@ -476,9 +418,9 @@ QPlatformOpenGLContext *QWindowsIntegration::createPlatformOpenGLContext(QOpenGL { qCDebug(lcQpaGl) << __FUNCTION__ << context->format(); if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { - QScopedPointer<QWindowsOpenGLContext> result(staticOpenGLContext->createContext(context)); + std::unique_ptr<QWindowsOpenGLContext> result(staticOpenGLContext->createContext(context)); if (result->isValid()) - return result.take(); + return result.release(); } return nullptr; } @@ -508,12 +450,12 @@ QOpenGLContext *QWindowsIntegration::createOpenGLContext(HGLRC ctx, HWND window, return nullptr; if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) { - QScopedPointer<QWindowsOpenGLContext> result(staticOpenGLContext->createContext(ctx, window)); + std::unique_ptr<QWindowsOpenGLContext> result(staticOpenGLContext->createContext(ctx, window)); if (result->isValid()) { auto *context = new QOpenGLContext; context->setShareContext(shareContext); auto *contextPrivate = QOpenGLContextPrivate::get(context); - contextPrivate->adopt(result.take()); + contextPrivate->adopt(result.release()); return context; } } @@ -537,17 +479,17 @@ QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext() 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(); +#if QT_CONFIG(directwrite3) + if (!(d->m_options & (QWindowsIntegration::FontDatabaseGDI | QWindowsIntegration::DontUseDirectWriteFonts))) + d->m_fontDatabase = new QWindowsDirectWriteFontDatabase; + else +#endif + d->m_fontDatabase = new QWindowsFontDatabase; } return d->m_fontDatabase; } @@ -595,14 +537,9 @@ QVariant QWindowsIntegration::styleHint(QPlatformIntegration::StyleHint hint) co return QPlatformIntegration::styleHint(hint); } -Qt::KeyboardModifiers QWindowsIntegration::queryKeyboardModifiers() const -{ - return QWindowsKeyMapper::queryKeyboardModifiers(); -} - -QList<int> QWindowsIntegration::possibleKeys(const QKeyEvent *e) const +QPlatformKeyMapper *QWindowsIntegration::keyMapper() const { - return d->m_context.possibleKeys(e); + return d->m_context.keyMapper(); } #if QT_CONFIG(clipboard) @@ -649,12 +586,12 @@ QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const QStringList QWindowsIntegration::themeNames() const { - return QStringList(QLatin1String(QWindowsTheme::name)); + return QStringList(QLatin1StringView(QWindowsTheme::name)); } QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const { - if (name == QLatin1String(QWindowsTheme::name)) + if (name == QLatin1StringView(QWindowsTheme::name)) return new QWindowsTheme; return QPlatformIntegration::createPlatformTheme(name); } @@ -669,6 +606,161 @@ void QWindowsIntegration::beep() const MessageBeep(MB_OK); // For QApplication } +void QWindowsIntegration::setApplicationBadge(qint64 number) +{ + // Clamp to positive numbers, as the Windows API doesn't support negative numbers + number = qMax(0, number); + + // Persist, so we can re-apply it on setting changes and Explorer restart + m_applicationBadgeNumber = number; + + static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; + +#if QT_CONFIG(cpp_winrt) + // We prefer the native BadgeUpdater API, that allows us to set a number directly, + // but it requires that the application has a package identity, and also doesn't + // seem to work in all cases on < Windows 11. + QT_TRY { + if (isWindows11 && qt_win_hasPackageIdentity()) { + using namespace winrt::Windows::UI::Notifications; + auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber); + badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number))); + BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml)); + return; + } + } QT_CATCH(...) { + // fall back to win32 implementation + } +#endif + + // Fallback for non-packaged apps, Windows 10, or Qt builds without WinRT/C++ support + + if (!number) { + // Clear badge + setApplicationBadge(QImage()); + return; + } + + const bool isDarkMode = QWindowsTheme::instance()->colorScheme() + == Qt::ColorScheme::Dark; + + QColor badgeColor; + QColor textColor; + +#if QT_CONFIG(cpp_winrt) + if (isWindows11) { + // Match colors used by BadgeUpdater + static const auto fromUIColor = [](winrt::Windows::UI::Color &&color) { + return QColor(color.R, color.G, color.B, color.A); + }; + using namespace winrt::Windows::UI::ViewManagement; + const auto settings = UISettings(); + badgeColor = fromUIColor(settings.GetColorValue(isDarkMode ? + UIColorType::AccentLight2 : UIColorType::Accent)); + textColor = fromUIColor(settings.GetColorValue(UIColorType::Background)); + } +#endif + + if (!badgeColor.isValid()) { + // Fall back to basic badge colors, based on Windows 10 look + badgeColor = isDarkMode ? Qt::black : QColor(220, 220, 220); + badgeColor.setAlphaF(0.5f); + textColor = isDarkMode ? Qt::white : Qt::black; + } + + const auto devicePixelRatio = qApp->devicePixelRatio(); + + static const QSize iconBaseSize(16, 16); + QImage image(iconBaseSize * devicePixelRatio, + QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + + QPainter painter(&image); + + QRect badgeRect = image.rect(); + QPen badgeBorderPen = Qt::NoPen; + if (!isWindows11) { + QColor badgeBorderColor = textColor; + badgeBorderColor.setAlphaF(0.5f); + badgeBorderPen = badgeBorderColor; + badgeRect.adjust(1, 1, -1, -1); + } + painter.setBrush(badgeColor); + painter.setPen(badgeBorderPen); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawEllipse(badgeRect); + + auto pixelSize = qCeil(10.5 * devicePixelRatio); + // Unlike the BadgeUpdater API we're limited by a square + // badge, so adjust the font size when above two digits. + const bool textOverflow = number > 99; + if (textOverflow) + pixelSize *= 0.8; + + QFont font = painter.font(); + font.setPixelSize(pixelSize); + font.setWeight(isWindows11 ? QFont::Medium : QFont::DemiBold); + painter.setFont(font); + + painter.setRenderHint(QPainter::TextAntialiasing, devicePixelRatio > 1); + painter.setPen(textColor); + + auto text = textOverflow ? u"99+"_s : QString::number(number); + painter.translate(textOverflow ? 1 : 0, textOverflow ? 0 : -1); + painter.drawText(image.rect(), Qt::AlignCenter, text); + + painter.end(); + + setApplicationBadge(image); +} + +void QWindowsIntegration::setApplicationBadge(const QImage &image) +{ + QComHelper comHelper; + + using Microsoft::WRL::ComPtr; + + ComPtr<ITaskbarList3> taskbarList; + CoCreateInstance(CLSID_TaskbarList, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskbarList)); + if (!taskbarList) { + // There may not be any windows with a task bar button yet, + // in which case we'll apply the badge once a window with + // a button has been created. + return; + } + + const auto hIcon = image.toHICON(); + + // Apply the icon to all top level windows, since the badge is + // set on an application level. If one of the windows go away + // the other windows will take over in showing the badge. + const auto topLevelWindows = QGuiApplication::topLevelWindows(); + for (auto *topLevelWindow : topLevelWindows) { + if (!topLevelWindow->handle()) + continue; + auto hwnd = reinterpret_cast<HWND>(topLevelWindow->winId()); + taskbarList->SetOverlayIcon(hwnd, hIcon, L""); + } + + DestroyIcon(hIcon); + + // FIXME: Update icon when the application scale factor changes. + // Doing so in response to screen DPI changes is too soon, as the + // task bar is not yet ready for an updated icon, and will just + // result in a blurred icon even if our icon is high-DPI. +} + +void QWindowsIntegration::updateApplicationBadge() +{ + // The system color settings have changed, or we are reacting + // to a task bar button being created for the fist time or after + // Explorer had crashed and re-started. In any case, re-apply the + // badge so that everything is up to date. + if (m_applicationBadgeNumber) + setApplicationBadge(m_applicationBadgeNumber); +} + #if QT_CONFIG(vulkan) QPlatformVulkanInstance *QWindowsIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const { |