diff --git a/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp b/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp new file mode 100644 index 00000000..487e1d47 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -0,0 +1,771 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2013 Samuel Gaist +// 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" +#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 +#include + +#include + +#include + +#if !defined(QT_NO_OPENGL) +# include "qwindowsglcontext.h" +#endif + +#include "qwindowsopengltester.h" + +#if QT_CONFIG(cpp_winrt) +# include +# include +# include +# include +# include +#endif + +#include + +static inline void initOpenGlBlacklistResources() +{ + Q_INIT_RESOURCE(openglblacklists); +} + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +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 QLatin1StringView &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 >= int(minimumValue) && value <= int(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::DpiAwareness *dpiAwareness, + DarkModeHandling *darkModeHandling) +{ + unsigned options = 0; + for (const QString ¶m : paramList) { + if (param.startsWith(u"fontengine=")) { + if (param.endsWith(u"gdi")) { + options |= QWindowsIntegration::FontDatabaseGDI; + } 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, "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"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); + } 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::DpiAwareness dpiAwareness = QtWindows::DpiAwareness::PerMonitorVersion2; + + int tabletAbsoluteRange = -1; + 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); + + 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)) { + m_context.setProcessDpiAwareness(dpiAwareness); + qCDebug(lcQpaWindow) << "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().initialize(); + d->m_context.setDetectAltGrModifier((d->m_options & DetectAltGrModifier) != 0); +} + +QWindowsIntegration::~QWindowsIntegration() +{ + m_instance = nullptr; +} + +void QWindowsIntegration::initialize() +{ + 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 +{ + 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 + case BackingStoreStaticContents: + return true; + default: + return QPlatformIntegration::hasCapability(cap); + } + return false; +} + +QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) const +{ + if (window->type() == Qt::Desktop) { + auto *result = new QWindowsDesktopWindow(window); + qCDebug(lcQpaWindow) << "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); + 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(customMarginsV); + } + + QWindowsWindowData obtained = + QWindowsWindowData::create(window, requested, + QWindowsWindow::formatWindowTitle(window->title())); + qCDebug(lcQpaWindow).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(lcQpaWindow) << "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()) { + std::unique_ptr result(staticOpenGLContext->createContext(context)); + if (result->isValid()) + return result.release(); + } + 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()) { + std::unique_ptr result(staticOpenGLContext->createContext(ctx, window)); + if (result->isValid()) { + auto *context = new QOpenGLContext; + context->setShareContext(shareContext); + auto *contextPrivate = QOpenGLContextPrivate::get(context); + contextPrivate->adopt(result.release()); + 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) { +#ifndef QT_NO_FREETYPE + if (d->m_options & QWindowsIntegration::FontDatabaseFreeType) + d->m_fontDatabase = new QWindowsFontDatabaseFT; + else +#endif // QT_NO_FREETYPE +#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; +} + +#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); +} + +QPlatformKeyMapper *QWindowsIntegration::keyMapper() const +{ + return d->m_context.keyMapper(); +} + +#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(QLatin1StringView(QWindowsTheme::name)); +} + +QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const +{ + if (name == QLatin1StringView(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 +} + +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 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(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 +{ + return new QWindowsVulkanInstance(instance); +} +#endif + +QT_END_NAMESPACE